解决上传文件时报错提示assertion (isArray) failed: not array的问题
===================================================

#### 问题现象

用request.uploadFile报错assertion (isArray) failed: not array。报错代码如下：

uploadimgWrong() {
  let uploadConfig: request.UploadConfig = {
   _ // 此处修改为正确的服务器url_
    url: "",
    header: { 'Content-Type': 'multipart/form-data', "Accept": "\*/\*" },
    method: "POST",
    files: this.fileList,
    data: \[{
      name: "email",
      value: 'xxxx',
    }, {
      name: "phone",
      value: 'xxxxxxxx',
    }, {
      name: "content",
      value: '测试',
    }, {
      name: "type",
      value: '4',
    }\],
  };
  try {
    request.uploadFile(this.getUIContext().getHostContext(),
      uploadConfig,
      (err: BusinessError, uploadTask: request.UploadTask) => {
        if (err) {
          return;
        }
        let upCompleteCallback = () => {
        };
        uploadTask.on('complete', upCompleteCallback);
        let upFailCallback = () => {
        };
        uploadTask.on('fail', upFailCallback);
      });
  } catch (err) {
    hilog.error(DOMAIN, 'testTag', 'Failed to uploadFile', JSON.stringify(err));
  }

}

#### 背景知识

[request.uploadFile](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-request#requestuploadfile9-1)：创建并启动一个上传任务，使用callback异步回调，支持HTTP协议。通过[on('complete'|'fail')](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-request#oncomplete--fail9)可获取任务上传时的成功信息或错误信息。

[UploadConfig](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-request#uploadconfig)：上传任务的配置信息。

#### 问题定位

[UploadConfig](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-request#uploadconfig)中的参数files用的是[File](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-request#file)，而问题代码中用到的file是自定义的类。所以在上传文件时报错assertion (isArray) failed: not array。

#### 分析结论

在上传文件时，传入的UploadConfig要做到每个入参都能对应上。

#### 修改建议

声明一个Array<request.File>，赋值后以传入到uploadConfig中。示例代码如下：

uploadimgRight() {
  let files: Array<request.File> = \[\];
  for (let i = 0; i < this.fileList.length; i++) {
    files.push({
      filename: this.fileList\[i\].filename,
      name: this.fileList\[i\].name,
      type: this.fileList\[i\].type,
      uri: this.fileList\[i\].uri
    });
  }
  let uploadConfig: request.UploadConfig = {
   _ // 此处修改为正确的服务器url_
    url: "",
    header: { 'Content-Type': 'multipart/form-data', "Accept": "\*/\*" },
    method: "POST",
    files: files,
    data: \[{
      name: "email",
      value: 'xxx',
    }, {
      name: "phone",
      value: 'xxxx',
    }, {
      name: "content",
      value: '测试',
    }, {
      name: "type",
      value: '4',
    }\],
  };

  request.uploadFile(this.getUIContext().getHostContext(),
    uploadConfig,
    (err: BusinessError, uploadTask: request.UploadTask) => {
      if (err) {
        return;
      }
      let upCompleteCallback = (taskStates: Array<request.TaskState>) => {
        for (let i = 0; i < taskStates.length; i++) {
          console.info("upOnComplete taskState:" + JSON.stringify(taskStates\[i\]));
        }
      };
      uploadTask.on('complete', upCompleteCallback);
      let upFailCallback = () => {
      };
      uploadTask.on('fail', upFailCallback);
    });

}

全量代码如下：

import { BusinessError, request } from '@kit.BasicServicesKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import fs from '@ohos.file.fs';
import { image } from '@kit.ImageKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

const MAX\_SIZE = 800;
const DOMAIN = 0x0000;

@Entry
@ComponentV2
export struct AskQuestion {
  @Local refreshing: boolean = false;
  @Local refreshOffset: number = 0;
  @Local refreshState: RefreshStatus = RefreshStatus.Inactive;
  @Local scroller: Scroller = new Scroller();
  @Local fileList: Datafile\[\] = \[\];
  @Local data: myData = {};

  aboutToAppear() {
  }

  uploadimgRight() {
    let files: Array<request.File> = \[\];
    for (let i = 0; i < this.fileList.length; i++) {
      files.push({
        filename: this.fileList\[i\].filename,
        name: this.fileList\[i\].name,
        type: this.fileList\[i\].type,
        uri: this.fileList\[i\].uri
      });
    }
    let uploadConfig: request.UploadConfig = {
     _ // 此处修改为正确的服务器url_
      url: "",
      header: { 'Content-Type': 'multipart/form-data', "Accept": "\*/\*" },
      method: "POST",
      files: files,
      data: \[{
        name: "email",
        value: 'xxx',
      }, {
        name: "phone",
        value: 'xxxx',
      }, {
        name: "content",
        value: '测试',
      }, {
        name: "type",
        value: '4',
      }\],
    };

    request.uploadFile(this.getUIContext().getHostContext(),
      uploadConfig,
      (err: BusinessError, uploadTask: request.UploadTask) => {
        if (err) {
          return;
        }
        let upCompleteCallback = (taskStates: Array<request.TaskState>) => {
          for (let i = 0; i < taskStates.length; i++) {
            console.info("upOnComplete taskState:" + JSON.stringify(taskStates\[i\]));
          }
        };
        uploadTask.on('complete', upCompleteCallback);
        let upFailCallback = () => {
        };
        uploadTask.on('fail', upFailCallback);
      });

  }

  uploadimgWrong() {
    let uploadConfig: request.UploadConfig = {
     _ // 此处修改为正确的服务器url_
      url: "",
      header: { 'Content-Type': 'multipart/form-data', "Accept": "\*/\*" },
      method: "POST",
      files: this.fileList,
      data: \[{
        name: "email",
        value: 'xxxx',
      }, {
        name: "phone",
        value: 'xxxxxxxx',
      }, {
        name: "content",
        value: '测试',
      }, {
        name: "type",
        value: '4',
      }\],
    };
    try {
      request.uploadFile(this.getUIContext().getHostContext(),
        uploadConfig,
        (err: BusinessError, uploadTask: request.UploadTask) => {
          if (err) {
            return;
          }
          let upCompleteCallback = () => {
          };
          uploadTask.on('complete', upCompleteCallback);
          let upFailCallback = () => {
          };
          uploadTask.on('fail', upFailCallback);
        });
    } catch (err) {
      hilog.error(DOMAIN, 'testTag', 'Failed to uploadFile', JSON.stringify(err));
    }

  }

  build() {
    Column() {

      Column() {
        Row() {
          Text('上传图片错误').onClick(() => {
            this.uploadimgWrong();
          });
          Text('上传图片正确').onClick(() => {
            this.uploadimgRight();
          });
          Text("选择图片").onClick(() => {
            example({
              selectOptions: {
                maxSelectNumber: 5,
                selectType: "image",
              },
              onSuccess: (uris) => {
                this.fileList = uris;
              },
              onError: () => {
              },
              extraParam: '自定义参数值'
            }, this.getUIContext().getHostContext()?.cacheDir);
          });
        }.width("100%")
        .justifyContent(FlexAlign.SpaceBetween);

        List({ scroller: this.scroller }) {
          ForEach(this.fileList, (item: Datafile) => {
            ListItem() {
              Image(item.uri).width(40).height(40);
            };
          });
        }
        .layoutWeight(1);

        Image("file://" + this.getUIContext().getHostContext()?.cacheDir + "IMG\_20250704\_102801.jpg")
          .width(40)
          .height(40); _//只有当有file的时候才能展示图片，使用internal://cache/ 无法展示_
      }.padding({ left: 10, right: 10 });
    }.height("100%");

  }
}

class Datafile {
  filename: string = "";
  name: string = "";
  uri: string = "";
  type: string = "";
  url?: string = "";
}

class myData {
  email?: string = "";
  phone?: number = 0;
  content?: string = "";
  type?: number = 0;
  courseId?: number = 0;
}

export async function example(config: ExampleConfig, cacheDir?: string) {
  const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();

  if (config.selectOptions.selectType == "image") {
    photoSelectOptions.MIMEType =
      photoAccessHelper.PhotoViewMIMETypes.IMAGE\_TYPE;
  } else if (config.selectOptions.selectType == "video") {
    photoSelectOptions.MIMEType =
      photoAccessHelper.PhotoViewMIMETypes.VIDEO\_TYPE;
  } else if (config.selectOptions.selectType == "all") {
    photoSelectOptions.MIMEType =
      photoAccessHelper.PhotoViewMIMETypes.IMAGE\_VIDEO\_TYPE;
  }

  photoSelectOptions.maxSelectNumber =
    config.selectOptions.maxSelectNumber;

  const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
  photoViewPicker.select(photoSelectOptions)
    .then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
      const uris = photoSelectResult.photoUris;
      const fileList: Datafile\[\] = \[\];

     _ // 使用Promise.all处理多个文件_
      await Promise.all(uris.map(async (item) => {

        const filename = item.split('/').pop() || "";
        const lastDotIndex = filename.lastIndexOf('.');
        const name = lastDotIndex === -1 ? filename : filename.substring(0, lastDotIndex);

      _  // 只对图片进行压缩处理_
        if (photoSelectOptions.MIMEType === photoAccessHelper.PhotoViewMIMETypes.IMAGE\_TYPE ||
          photoSelectOptions.MIMEType === photoAccessHelper.PhotoViewMIMETypes.IMAGE\_VIDEO\_TYPE) {
          const compressedFile = await processImage(item, filename, name, cacheDir);
          fileList.push(compressedFile);
        } else {
        _  // 非图片文件直接复制_
          try {
            const fileHandle = fs.openSync(item, fs.OpenMode.READ\_ONLY);
            fs.copyFileSync(fileHandle.fd, \`${cacheDir}/${filename}\`);
            fs.closeSync(fileHandle);
          } catch (er) {
            hilog.error(DOMAIN, 'testTag', 'Failed to uploadFile', JSON.stringify(er));
          }

          fileList.push({
            filename: name,
            name: name,
            type: "image/jpeg",
            uri: "internal://cache/" + filename,
            url: "file://" + cacheDir + "/" + filename
          });
        }

      }));

      config.onSuccess(fileList);

    })
    .catch((err: BusinessError) => {
      config.onError(err);
    });
}

async function processImage(uri: string, filename: string, name: string, cacheDir?: string): Promise<Datafile> {
  const outputPath = \`${cacheDir}/${filename}\`;
  _// 1. 获取原始图片信息_
  const fileHandle = fs.openSync(uri, fs.OpenMode.READ\_ONLY);
  const imageSource = image.createImageSource(fileHandle.fd);
  const imageInfo = await imageSource.getImageInfo();
  fs.closeSync(fileHandle);
  let width = imageInfo.size.width;
  let height = imageInfo.size.height;

 _ // 2. 计算缩放比例_
  if (width > MAX\_SIZE || height > MAX\_SIZE) {
    const scaleRatio = Math.min(MAX\_SIZE / width, MAX\_SIZE / height);
    width = Math.floor(width \* scaleRatio);
    height = Math.floor(height \* scaleRatio);


 _   // 3. 重新打开文件进行压缩_
    const fileHandleForProcess = fs.openSync(uri, fs.OpenMode.READ\_ONLY);
    const imageSourceForProcess = image.createImageSource(fileHandleForProcess.fd);

   _ // 4. 创建压缩选项_
    const decodingOptions: ESObject = {
      desiredSize: ({ width: MAX\_SIZE, height: MAX\_SIZE } as desiredSize),
      rotateDegrees: 0,
      editable: false,
      desiredPixelFormat: image.PixelMapFormat.RGBA\_8888,
    };

   _ // 5. 创建像素图并压缩_
    const pixelMap = await imageSourceForProcess.createPixelMap(decodingOptions);
    const imagePacker = image.createImagePacker();
    const packOptions: ESObject = {
      format: 'image/jpeg', _// 可根据需要调整格式_
      quality: 80, _// 压缩质量 (0-100)_
    };

    _// 6. 保存压缩后的图片_
    const arrayBuffer = await imagePacker.packToData(pixelMap, packOptions);
    const uint8Array = new Uint8Array(arrayBuffer);

  _  // 3. 写入文件（严格正确的写法）_
    const fd = fs.openSync(outputPath, fs.OpenMode.CREATE | fs.OpenMode.READ\_WRITE);
    fs.writeSync(fd.fd, uint8Array.buffer, {
      length: uint8Array.byteLength  _// 必须指定长度！_
    });
    fs.closeSync(fd);

   _ // 7. 释放资源_
    pixelMap.release();
    imageSourceForProcess.release();
    fs.closeSync(fileHandleForProcess);
  } else {
   _ // 无需压缩，直接复制_
    const fileHandle = fs.openSync(uri, fs.OpenMode.READ\_ONLY);
    fs.copyFileSync(fileHandle.fd, outputPath);
    fs.closeSync(fileHandle);
  }

  return {
    filename: name,
    name: name,
    type: "image/jpeg",
    uri: "internal://cache/" + filename,
    url: "file://" + cacheDir + "/" + filename
  };

}

export interface ExampleConfig {
  selectOptions: select;
  onSuccess: SuccessCallback;
  onError: ErrorCallback;
  extraParam?: string;
}

interface select {
  maxSelectNumber: number;
  selectType: string;
}

type SuccessCallback = (uris: Datafile\[\]) => void;
type ErrorCallback = (error: BusinessError) => void;

_// 处理图片压缩_
interface desiredSize {
  width: number;
  height: number;
}