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中上传文件报错可能涉及以下技术点:
- 检查网络权限是否在config.json中正确配置;
- 确认使用的@ohos.request API版本适配当前系统;
- 文件路径需使用鸿蒙专用沙箱路径(如data/storage/…);
- 检查服务器TLS证书是否受鸿蒙信任库支持。
错误日志可通过hilog抓取,重点观察HTTP状态码和底层IO异常。
从代码和错误信息来看,上传文件失败的主要问题可能出在文件路径处理上。以下是关键问题点分析:
- 文件URI处理不正确:
- 当前代码使用
internal://cache/
前缀,但实际应该使用file://
协议 - 建议改为:
let realUri = "file://" + newPath;
- FormData处理问题:
- 直接传递URI字符串可能无法被正确解析
- 应该使用File对象或Blob对象:
const file = { uri: realUri, type: 'image/jpeg', name: resFile.name };
formData.append('file', file);
- 文件权限检查:
- 确保已添加文件访问权限到config.json:
"reqPermissions": [
{
"name": "ohos.permission.READ_MEDIA",
"reason": "需要读取文件"
},
{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "需要写入文件"
}
]
- 文件关闭时机:
- 当前代码在复制后立即关闭文件,可能导致上传时文件不可用
- 建议在上传完成后再关闭文件描述符
- 调试建议:
- 检查newPath是否确实包含有效文件
- 打印realUri值确认路径格式正确
- 使用adb shell查看沙箱目录文件是否存在
可以尝试先用固定测试文件测试上传功能,排除文件选择环节的问题。