本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新

鸿蒙(HarmonyOS)开发中,上传表单格式文件(包括multipart/form-data)是一个常见的需求,如:文件上传。

一、基本概念

表单文件上传通常使用multipart/form-data格式,这种格式可以同时上传文件和其他表单字段。在鸿蒙中,可以使用@ohos.net.http模块来实现。

二、核心API介绍

1. 主要类和方法

类/方法描述
http.createHttp()创建HTTP请求对象
HttpRequestHTTP请求类
RequestMethod请求方法枚举(POST, GET等)
RequestData请求数据类
ResponseData响应数据类
HttpResponseHTTP响应类

2. 文件上传相关方法

// 创建HTTP请求
const httpRequest = http.createHttp();

// 设置请求头
request.setHeader(headerKey, headerValue);

// 上传文件
request.upload(
  config: UploadRequest, 
  callback: AsyncCallback<UploadTask>
): void;

三、完整文件上传实现

1. 简单文件上传示例

import http from '@ohos.net.http';
import fileio from '@ohos.fileio';
import featureAbility from '@ohos.ability.featureAbility';

async function uploadFile(fileUri: string, uploadUrl: string) {
  // 1. 创建HTTP请求
  const httpRequest = http.createHttp();
  
  // 2. 获取文件信息
  const context = featureAbility.getContext();
  const filePath = await context.getFilesDir() + '/' + fileUri;
  
  // 3. 准备表单数据
  const formData = [
    {
      name: 'file', // 字段名
      filename: 'example.jpg', // 文件名
      type: 'image/jpeg', // MIME类型
      data: { // 文件数据
        uri: filePath,
        type: 'file',
      }
    },
    {
      name: 'description', // 普通表单字段
      value: 'This is a sample file upload' // 字段值
    }
  ];
  
  // 4. 设置请求头
  httpRequest.setHeader('Content-Type', 'multipart/form-data');
  
  // 5. 执行上传
  try {
    const response = await httpRequest.upload(
      uploadUrl,
      formData,
      { method: http.RequestMethod.POST }
    );
    
    console.log('Upload success:', response.result);
    return response.result;
  } catch (error) {
    console.error('Upload failed:', error);
    throw error;
  } finally {
    // 6. 销毁请求
    httpRequest.destroy();
  }
}

2. 带进度监控的文件上传

import http from '@ohos.net.http';

async function uploadWithProgress(filePath: string, uploadUrl: string) {
  const httpRequest = http.createHttp();
  
  // 准备表单数据
  const formData = [
    {
      name: 'file',
      filename: 'example.jpg',
      type: 'image/jpeg',
      data: {
        uri: filePath,
        type: 'file',
      }
    }
  ];
  
  // 创建上传任务
  const uploadTask = httpRequest.upload(
    uploadUrl,
    formData,
    { 
      method: http.RequestMethod.POST,
      // 进度回调
      progress: (uploaded: number, total: number) => {
        const progress = Math.round((uploaded / total) * 100);
        console.log(`Upload progress: ${progress}%`);
      }
    }
  );
  
  // 处理上传结果
  uploadTask.then((response) => {
    console.log('Upload completed:', response.result);
  }).catch((error) => {
    console.error('Upload error:', error);
  });
  
  // 可以取消上传
  // uploadTask.abort();
}

3. 多文件上传实现

import http from '@ohos.net.http';

async function uploadMultipleFiles(files: Array<{path: string, name: string}>, uploadUrl: string) {
  const httpRequest = http.createHttp();
  
  // 构建多文件表单数据
  const formData = files.map(file => ({
    name: 'files[]', // 注意数组表示法
    filename: file.name,
    type: this.getMimeType(file.name),
    data: {
      uri: file.path,
      type: 'file'
    }
  }));
  
  // 添加其他表单字段
  formData.push({
    name: 'userId',
    value: '12345'
  });
  
  try {
    const response = await httpRequest.upload(
      uploadUrl,
      formData,
      { method: http.RequestMethod.POST }
    );
    
    console.log('Multi-file upload success:', response.result);
    return response.result;
  } catch (error) {
    console.error('Multi-file upload failed:', error);
    throw error;
  } finally {
    httpRequest.destroy();
  }
}

// 获取文件MIME类型
function getMimeType(filename: string): string {
  const extension = filename.split('.').pop()?.toLowerCase() || '';
  switch (extension) {
    case 'jpg':
    case 'jpeg':
      return 'image/jpeg';
    case 'png':
      return 'image/png';
    case 'gif':
      return 'image/gif';
    case 'pdf':
      return 'application/pdf';
    case 'txt':
      return 'text/plain';
    default:
      return 'application/octet-stream';
  }
}

四、注意事项

1. 文件路径处理

获取文件路径的正确方式:

import featureAbility from '@ohos.ability.featureAbility';

// 获取应用文件目录
const context = featureAbility.getContext();
const filesDir = await context.getFilesDir();

// 构建完整文件路径
const filePath = filesDir + '/myfile.jpg';

2. 错误处理

完善的错误处理应该包括:

try {
  const response = await httpRequest.upload(...);
  
  // 检查HTTP状态码
  if (response.responseCode >= 400) {
    throw new Error(`Server returned ${response.responseCode}`);
  }
  
  // 处理响应数据
  const result = JSON.parse(response.result);
  
} catch (error) {
  // 分类处理不同错误
  if (error.code === 'ENOENT') {
    console.error('File not found');
  } else if (error.message.includes('network')) {
    console.error('Network error');
  } else {
    console.error('Unknown error:', error);
  }
}

3. 性能优化

对于大文件上传:

// 分块上传大文件
async function chunkedUpload(filePath: string, uploadUrl: string, chunkSize = 1024 * 1024) {
  const fileInfo = await fileio.stat(filePath);
  const fileSize = fileInfo.size;
  let offset = 0;
  
  while (offset < fileSize) {
    const end = Math.min(offset + chunkSize, fileSize);
    const chunkData = await fileio.read(filePath, { offset, length: end - offset });
    
    const formData = [
      {
        name: 'file',
        filename: 'chunk.dat',
        type: 'application/octet-stream',
        data: chunkData
      },
      {
        name: 'chunkInfo',
        value: JSON.stringify({
          fileSize,
          offset,
          totalChunks: Math.ceil(fileSize / chunkSize)
        })
      }
    ];
    
    await uploadChunk(uploadUrl, formData);
    offset = end;
  }
  
  // 通知服务器完成上传
  await notifyUploadComplete(uploadUrl, filePath);
}

五、完整示例:图片上传组件

@Entry
@Component
struct ImageUploader {
  @State progress: number = 0;
  @State uploadStatus: string = 'Ready';
  @State imageUri: string = '';
  
  private httpRequest: http.HttpRequest = http.createHttp();
  
  // 选择图片
  async pickImage() {
    try {
      const result = await picker.pick({
        type: picker.PickerType.IMAGE
      });
      
      if (result && result.length > 0) {
        this.imageUri = result[0].uri;
        this.uploadStatus = 'Image selected';
      }
    } catch (error) {
      console.error('Image pick error:', error);
      this.uploadStatus = 'Failed to select image';
    }
  }
  
  // 上传图片
  async uploadImage() {
    if (!this.imageUri) {
      this.uploadStatus = 'No image selected';
      return;
    }
    
    this.uploadStatus = 'Uploading...';
    this.progress = 0;
    
    const formData = [
      {
        name: 'image',
        filename: 'upload.jpg',
        type: 'image/jpeg',
        data: {
          uri: this.imageUri,
          type: 'file'
        }
      },
      {
        name: 'timestamp',
        value: new Date().getTime().toString()
      }
    ];
    
    try {
      const uploadTask = this.httpRequest.upload(
        'https://example.com/upload',
        formData,
        {
          method: http.RequestMethod.POST,
          progress: (uploaded, total) => {
            this.progress = Math.round((uploaded / total) * 100);
          }
        }
      );
      
      const response = await uploadTask;
      this.uploadStatus = 'Upload completed';
      console.log('Server response:', response.result);
      
    } catch (error) {
      console.error('Upload error:', error);
      this.uploadStatus = 'Upload failed';
    }
  }
  
  build() {
    Column({ space: 20 }) {
      if (this.imageUri) {
        Image(this.imageUri)
          .width(200)
          .height(200)
          .margin(10)
      }
      
      Button('Select Image')
        .onClick(() => this.pickImage())
        .width('80%')
      
      Button('Upload Image')
        .onClick(() => this.uploadImage())
        .width('80%')
        .enabled(!!this.imageUri)
      
      Progress({ value: this.progress, total: 100 })
        .width('80%')
      
      Text(this.uploadStatus)
        .fontSize(16)
    }
    .width('100%')
    .padding(20)
  }
  
  aboutToDisappear() {
    // 组件销毁时释放HTTP资源
    this.httpRequest.destroy();
  }
}

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐