HarmonyOS鸿蒙Next中上传文件一直报错

HarmonyOS鸿蒙Next中上传文件一直报错

上传文件一直报错:Failed to open/read local data from file/application 看论坛里面说是先保存到沙箱再上传,我这么上传好像还是不行啊! 大佬救命 大佬救命 大佬救命

上传代码:

PhotoHelper.select().then((uris) => {
    let ps: Permissions[] = ['ohos.permission.WRITE_IMAGEVIDEO'];
    PermissionUtil.requestPermissions(ps).then((result) => {
        if (result) {
            DialogHelper.showLoadingDialog({ loadColor: $r('app.color.common_f5f6f6'), dialogId: "selectPhoto" })
            uris.forEach((uri, index) => {

                const resFile = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY)
                let newPath = getContext(this).cacheDir + "/" + resFile.name;
                fileIo.copyFileSync(resFile.fd, newPath)
                fileIo.closeSync(resFile.fd)
                let realUri = "internal://cache/" + resFile.name;
                const formData = new FormData()
                formData.append('contractId', CommonConstants.CONTRACT_ID); // 假设后端接收的文件字段名为 'file'
                formData.append('file',realUri);
                formData.append('fileName', resFile.name);
                formData.append('originalFile',realUri );
                formData.append('originalFileName', resFile.name);
                formData.append('fileParam', JSONUtil.beanToJsonStr(photoList[0]));
                RequestAxios.post<PhotoEntity>("wtis-core/file/uploadFileV2", formData).then((item) => {
                    LogUtil.debug('axios请求成功:',JSON.stringify(item))
                })

                // ImageHelper.compressPhoto(uri, this.filePath, index)
                //   .then((compressUri) => {
                //     const newPhoto = ImageHelper.savePhotoEntity(uri, compressUri);
                //     photoList.push(newPhoto);
                //     uriList.push(uri);
                //   });
            });
            DialogHelper.closeDialog("selectPhoto")
            EmitterUtil.post<string>("refresh_enum", "camera_select");
        } else {
            showToast("请在设置中打开权限");
            WantUtil.toAppSetting();
        }
    });
});

网络封装:

import axios, { InternalAxiosRequestConfig, AxiosError, AxiosResponse, AxiosRequestConfig } from '@ohos/axios'
import { promptAction } from '@kit.ArkUI'
import { MMKV } from '@tencent/mmkv';
import { LogUtil } from '@pura/harmony-utils';

let mmkv = MMKV.defaultMMKV();

// 定义API错误类型,用于统一错误处理
export interface APIErrorType {
  message: string
  msg: string
  code: string
}

//1. 实例化 通用配置   创建axios实例,配置基础URL和请求超时时间
const instance = axios.create({
  baseURL: 'http://xx.xx.xx.xx:30088/',//项目后端接口网址
  timeout: 5000
})

// 拦截器配置
// 请求拦截器
// token配置等
// 2.配置请求拦截器,用于处理请求发送前的操作,例如添加token
instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  const token = mmkv.decodeString("token") || ""
  const userName = mmkv.decodeString("userName") || ""
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}`
  }
  config.headers['creator'] = userName
  config.headers['updator'] = userName
  config.headers['Content-Type'] = 'multipart/form-data'
  return config
}, (error: AxiosError) => {
  // 如果请求错误,返回错误
  return Promise.reject(error)
})

// 添加响应拦截器
// 错误统一处理等
// 3.配置响应拦截器,用于处理接收到响应后的操作,例如错误处理
instance.interceptors.response.use((response: AxiosResponse) => {
  LogUtil.debug('上传成功:',JSON.parse(response.data))
  return response
}, (error: AxiosError<APIErrorType>) => {
  // 400 打印错误信息   根据不同的错误状态码进行不同的处理
  if (error.response?.status == 400) {
    // 打印错误信息
    promptAction.showToast({
      message: error.response.data.message
    })
  } else if (error.response?.status == 401) {
    // 显示登录过期信息并跳转到登录页面
    promptAction.showToast({
      message: '登录过期'
    })
  }
  // 返回错误,以便后续处理
  return Promise.reject(error)
})

// 定义HTTP响应类型,用于统一响应数据结构
export interface HttpResponse<T> {
  result: T;
  errorMSG?: string;
  isSuccess?: String;
}

// 定义响应类型,结合AxiosResponse和HttpResponse
export type ResponseType<T> = AxiosResponse<HttpResponse<T>>

// RequestAxios类,用于发起HTTP请求 RequestAxios的名字可以自己写一般通常就是Http,axios,request
export class RequestAxios {
  static get<T>(url: string, config?: AxiosRequestConfig): Promise<ResponseType<T>> {
    return instance.get<null, ResponseType<T>>(url, config)
  }

  static post<T>(url: string, data?: object): Promise<ResponseType<T>> {
    return instance.post<null, ResponseType<T>>(url, data)
  }

  static delete<T>(url: string, data?: object): Promise<ResponseType<T>> {
    return instance.delete<null, ResponseType<T>>(url, data)
  }

  static put<T>(url: string, data?: object): Promise<ResponseType<T>> {
    return instance.put<null, ResponseType<T>>(url, data)
  }
}

调试信息: 调试信息


更多关于HarmonyOS鸿蒙Next中上传文件一直报错的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

文件都读不到:我之前写了一个文件上传的博客你可以参考一下文件读取那一部分

HarmoyOS Next元服务图片上传组件深度实战:从零构建企业级文件传输方案

@StorageProp(BreakPointKey) breakPointKey: string = 'sm' // 断点状态变量
@Prop canUpload: boolean = true // 默认可上传
title: string = "111"
maxNumber: number = 9
maxSelectNumber: number = 9
@State
index: number = -1
@Prop imgList: ImageList[] = []
@State uploadProgress: number = 0
@State uploadUrl: string[] = []
preview: CustomDialogController = new CustomDialogController({
  autoCancel: false,
  customStyle: true,
  alignment: DialogAlignment.Center,
  builder: ImagePreview({
    urls: this.uploadUrl,
    selectIndex: this.index
  })
})
uploading: CustomDialogController = new CustomDialogController({
  autoCancel: false,
  customStyle: true,
  alignment: DialogAlignment.Center,
  builder: ImageLoading()
})
successUpNum: number = 0 // 当前选择成功上传的数量
failedUpNum: number = 0 // 当前选择失败上传的数量
allUpNum: number = 0 // 当前选择全部上传的数量
isUpload: boolean = false
upFilePath: string = ''
onListChange: (list: string[]) => void = () => {}
onUploadIsComplete: (uploadUrl: string[]) => void = () => {}
onUploadStateChange: (state: boolean) => void = () => {}

aboutToAppear(): void {
}

async selectImage() {
  this.successUpNum = 0
  this.failedUpNum = 0
  const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
  photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
  photoSelectOptions.maxSelectNumber = this.maxSelectNumber;
  photoSelectOptions.isOriginalSupported = true;
  photoSelectOptions.subWindowName = '选择上传图片';

  const photoPicker = new photoAccessHelper.PhotoViewPicker();
  const result = await photoPicker.select(photoSelectOptions);

  if (result.photoUris?.length) {
    this.imgList = [
      ...this.imgList,
      ...result.photoUris.map(url => ({ url } as ImageList))
    ];

    const folderName = 'upload_cache';
    const defaultDir = `${getContext(this).cacheDir}/${folderName}`;
    this.upFilePath = defaultDir;

    if (!fs.listFileSync(getContext(this).cacheDir).includes(folderName)) {
      fs.mkdirSync(defaultDir); // 确保目标目录存在
    }

    const files = result.photoUris.map(url => {
      const file = fs.openSync(url, fs.OpenMode.READ_ONLY);
      const tempFileName = `${util.generateRandomUUID()}.jpg`;
      const fileUri = `${defaultDir}/${tempFileName}`;

      fs.copyFileSync(file.fd, fileUri); // 拷贝文件到缓存目录
      fs.closeSync(file.fd);

      return {
        name: 'file',
        filename: tempFileName,
        type: 'jpg',
        uri: `internal://cache/${folderName}/${tempFileName}`
      } as request.File;
    });
    this.uploading.open()
    this.isUpload = true
    // 上传文件
    this.allUpNum = files.length
    for (let i = 0; i < files.length; i++) {
      await this.upload(files[i])
        .then(event => {
          this.successUpNum++
        })
        .catch(() => {
          this.failedUpNum++
        })
        .finally(() => {
          LogUtil.i('asda', this.successUpNum + this.failedUpNum + "aa" + this.allUpNum)
          if (this.allUpNum === this.successUpNum + this.failedUpNum) {
            this.uploading.close()
            this.isUpload = false
          }
        })
    }
  }
}

async upload(file: request.File) {
  const apiBaseURL = RequestAxios.baseApiUrl;
  const tag = 'uploadFiles';
  const formData = new FormData();
  formData.append('file', file['uri']);

  const openID =
    LoginStorageHandel.openID ||
      (PreferencesUtil.getSync(StorageConstant.*****, 'default_openID') as string)

  try {
    const response: AxiosResponse<UpLoadResponse> = await axios.post<string, AxiosResponse<UpLoadResponse>, FormData>(
      `${apiBaseURL}/************`,
      formData,
      {
        headers: {
          versionCode: AppUtil.getVersionCode(),
          'content-type': 'multipart/form-data',
          Connection: 'keep-alive'
        },
        context: getContext(this),
        onUploadProgress: (progressEvent) => {
          if (progressEvent?.loaded && progressEvent?.total) {
            this.uploadProgress = Math.ceil((progressEvent.loaded / progressEvent.total) * 100);
            if (this.uploadProgress === 100) {
              LogUtil.i(tag, `上传完成: ${this.uploadProgress}%`);
            }
          }
        }
      }
    )

    if (response.data.code === 500) {
      showToast('上传失败');
      LogUtil.e(tag, '上传失败', response);
      return Promise.reject()
    }
    LogUtil.i('asda', file['uri'][0])
    this.uploadUrl.push(response.data.url);
    this.onListChange(this.uploadUrl);

    if (this.imgList.length === this.uploadUrl.length) {
      this.onUploadIsComplete(this.uploadUrl);
      showToast('文件上传完成');
    }
    return Promise.resolve()
  } catch (error) {
    LogUtil.e(tag, error);
    return Promise.reject(error)
  }
}

重点看一下文件操作的问题

更多关于HarmonyOS鸿蒙Next中上传文件一直报错的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


uris的问题,要把相册中的图片copy到沙箱,然后返回沙箱路径,用沙箱路径进行上传即可

在HarmonyOS Next中上传文件报错可能涉及以下技术点:

  1. 检查网络权限是否在config.json中正确配置;
  2. 确认使用的@ohos.request API版本适配当前系统;
  3. 文件路径需使用鸿蒙专用沙箱路径(如data/storage/…);
  4. 检查服务器TLS证书是否受鸿蒙信任库支持。

错误日志可通过hilog抓取,重点观察HTTP状态码和底层IO异常。

从代码和错误信息来看,上传文件失败的主要问题可能出在文件路径处理上。以下是关键问题点分析:

  1. 文件URI处理不正确:
  • 当前代码使用internal://cache/前缀,但实际应该使用file://协议
  • 建议改为:let realUri = "file://" + newPath;
  1. FormData处理问题:
  • 直接传递URI字符串可能无法被正确解析
  • 应该使用File对象或Blob对象:
const file = { uri: realUri, type: 'image/jpeg', name: resFile.name };
formData.append('file', file);
  1. 文件权限检查:
  • 确保已添加文件访问权限到config.json:
"reqPermissions": [
  {
    "name": "ohos.permission.READ_MEDIA",
    "reason": "需要读取文件"
  },
  {
    "name": "ohos.permission.WRITE_MEDIA", 
    "reason": "需要写入文件"
  }
]
  1. 文件关闭时机:
  • 当前代码在复制后立即关闭文件,可能导致上传时文件不可用
  • 建议在上传完成后再关闭文件描述符
  1. 调试建议:
  • 检查newPath是否确实包含有效文件
  • 打印realUri值确认路径格式正确
  • 使用adb shell查看沙箱目录文件是否存在

可以尝试先用固定测试文件测试上传功能,排除文件选择环节的问题。

回到顶部