react-native项目通过华为OBS预签名url实现前端直传
·
react-native项目通过华为OBS预签名url实现前端直传
后端具体实现参照官方链接:https://support.huaweicloud.com/bestpractice-obs/obs_05_1203.html
抱歉我实在是找不到华为obs预签名url前端直传的demo,自己搞了老半天,而且rn和js的调用方式还有区别,真是搞死人,在这里我给广大前端开发者出一个例子,减轻一些负担
下面是前端实现的一个util
import toast from './toast';
import {Alert} from 'react-native';
import request from './request';
/**
* 获取预签名url
* @param param0
* @returns
*/
const getPresignedUrlForOBS = ({
fileName,
mimeType,
}: {
fileName: string;
mimeType: string;
}) => {
return request({
url: `/file/getObsUrl?fileName=${fileName}`,
method: 'GET',
});
};
/**
* 文件上传到obs
* @param param0
* @returns
*/
const uploadFile = async (file: {
uri: string;
type: string;
fileName: string;
}) => {
const {uri, type, fileName} = file;
try {
// 1. 获取预签名 URL(使用 text 流程验证过的接口)
const presignedRes: any = await getPresignedUrlForOBS({
fileName: fileName || 'image.jpg',
mimeType: type || 'image/jpeg',
});
if (presignedRes.code !== 200) {
toast(presignedRes.msg || '获取签名失败');
return;
}
const signedUrl = presignedRes.data.signedUrl;
console.log('Signed URL:', signedUrl);
// 2. 关键:使用 fetch 读取本地图片为 Blob
let blob;
try {
const response = await fetch(uri);
blob = await response.blob();
// 加强验证:确保读到了数据
if (blob.size === 0) {
throw new Error('文件读取为空,请检查 uri 是否有效');
}
console.log('成功读取图片 Blob,大小:', blob.size, '类型:', blob.type);
} catch (readError) {
console.error('读取本地文件失败:', readError);
throw new Error(`无法读取图片文件,请重试。路径: ${uri}`);
}
// 3. 准备请求头(使用签名时的 Content-Type)
const headers = {
'Content-Type': type || 'image/jpeg',
// 如果后端返回了其他签名头(如 x-obs-*),可合并
...presignedRes.data.actualSignedRequestHeaders,
};
// 4. 使用 fetch 上传(推荐,比 axios 更稳定)
const uploadResponse = await fetch(signedUrl, {
method: 'PUT',
headers,
body: blob, // 真正的二进制数据
});
if (!uploadResponse.ok) {
const errorText = await uploadResponse.text();
console.error('Upload failed:', errorText);
throw new Error(`上传失败: ${uploadResponse.status} ${errorText}`);
}
console.log('图片上传成功!状态:', uploadResponse.status);
// 5. 返回成功结果
return signedUrl.split('?')[0];
} catch (e) {
Alert.alert('上传失败', `${fileName} 上传失败,请重试。`);
console.error('OBS Upload Error:', e);
return '';
}
};
// 测试预签名 PUT是否可用
const testTextUpload = async () => {
const text =
'Hello from OBS test!\nThis is a simple text upload.\nTime: ' +
new Date().toISOString();
const fileName = 'test.txt';
const contentType = 'text/plain'; // 关键:文本类型
try {
// 1. 请求后端获取预签名 PUT URL
const presignedRes: any = await getPresignedUrlForOBS({
fileName,
mimeType: contentType,
});
const signedUrl = presignedRes.data.signedUrl;
console.log('获取预签名URL:', signedUrl);
// 2. 使用 PUT 上传纯文本
const uploadRes = await fetch(signedUrl, {
method: 'PUT',
headers: {
'Content-Type': contentType,
...presignedRes.data.actualSignedRequestHeaders,
},
body: text, // 直接传字符串!
});
console.log('uploadRes', uploadRes);
} catch (error: any) {
console.error('上传异常:', error);
Alert.alert('错误', error.message || '未知错误');
}
};
export {uploadFile, testTextUpload};
2025-10-10发现问题:通过实践,发现大概200M以上文件通过await fetch(uri)无法读取,被内存限制,不得已需要更改上传方案
需要另外集成"react-native-blob-util": "~0.17.0"组件,通过该组件替代fetch
下面是修改后的方法:
import RNBlobUtil from 'react-native-blob-util';
...其他代码相同,从const response = await fetch(uri);开始修改
let uploadTask = RNBlobUtil.fetch(
'PUT',
signedUrl,
{
'Content-Type': type,
...presignedRes.data.actualSignedRequestHeaders,
},
RNBlobUtil.wrap(uri), //这里 wrap 是合法的 body
);
//上传进度,如果是图片则不需要展示进度
if (type.indexOf('image') == -1) {
uploadTask.uploadProgress(
{
count: 10, // 每 10 个周期触发一次(可选)
},
(sent: number, total: number) => {
const a = ((sent / total) * 100).toFixed(2);
console.log(`文件上传中,请勿离开当前页面... 已上传${a}% `);
// 更新 UI 进度条
},
);
}
const response = await uploadTask;
console.log('response', response);
if (response?.respInfo?.status != 200) {
throw new Error(`上传失败,请稍候重试!`);
}
这里是我自己最终整个Util文件代码
import toast from './toast';
import {Alert} from 'react-native';
import request from './request';
import RNBlobUtil from 'react-native-blob-util';
import {EasyLoading} from '../components/EasyLoading/easyLoading';
/**
* 获取预签名url
* @param param0
* @returns
*/
const getPresignedUrlForOBS = ({
fileName,
mimeType,
}: {
fileName: string;
mimeType: string;
}) => {
return request({
url: `/file/getObsUrl?fileName=${fileName}`,
method: 'GET',
});
};
/**
* 文件上传到OBS(跨平台优化版,支持大文件)
* @param file { uri: string; type: string; fileName: string }
* @returns 成功返回公开URL,失败返回空字符串
*/
const uploadFile = async (file: {
uri: string;
type: string;
fileName: string;
}) => {
const {uri, type, fileName} = file;
try {
// 1. 获取预签名 URL
const presignedRes: any = await getPresignedUrlForOBS({
fileName: fileName || 'image.jpg',
mimeType: type || 'image/jpeg',
});
if (presignedRes.code !== 200) {
toast(presignedRes.msg || '获取签名失败');
return '';
}
const signedUrl = presignedRes.data.signedUrl;
console.log('Signed URL:', signedUrl);
// const response = await fetch(uri);
// const blob = await response.blob();
// if (blob.size === 0) {
// throw new Error('文件读取为空,请检查 uri 是否有效');
// }
// console.log(
// `📌 准备上传 iOS 文件,大小: ${(blob.size / 1024 / 1024).toFixed(
// 2,
// )} MB`,
// );
// const uploadResponse = await fetch(signedUrl, {
// method: 'PUT',
// headers: {
// 'Content-Type': type || 'application/octet-stream',
// ...presignedRes.data.actualSignedRequestHeaders,
// },
// body: blob,
// });
let uploadTask = RNBlobUtil.fetch(
'PUT',
signedUrl,
{
'Content-Type': type,
// 其他 headers(注意:预签名 URL 的鉴权头一般不需要额外加)
...presignedRes.data.actualSignedRequestHeaders,
},
RNBlobUtil.wrap(uri), //这里 wrap 是合法的 body
);
//上传进度,如果是图片则不需要展示进度
if (type.indexOf('image') == -1) {
uploadTask.uploadProgress(
{
count: 10, // 每 10 个周期触发一次(可选)
},
(sent: number, total: number) => {
const a = ((sent / total) * 100).toFixed(2);
console.log(`文件上传中,请勿离开当前页面... 已上传${a}% `);
EasyLoading.show(`文件上传中,请勿离开当前页面... 已上传${a}% `);
},
);
}
const response = await uploadTask;
console.log('response', response);
if (response?.respInfo?.status != 200) {
throw new Error(`上传失败,请稍候重试!`);
}
//上传成功
const result = signedUrl.split('?')[0];
console.log('上传成功:', result);
return result;
} catch (e: any) {
console.error('OBS Upload Error:', e);
Alert.alert(
'上传失败',
`${fileName} 上传失败,请重试。\n错误: ${e.message}`,
);
return '';
}
};
const testTextUpload = async () => {
const text =
'Hello from OBS test!\nThis is a simple text upload.\nTime: ' +
new Date().toISOString();
const fileName = 'test.txt';
const contentType = 'text/plain'; // 关键:文本类型
try {
// 1. 请求后端获取预签名 PUT URL
const presignedRes: any = await getPresignedUrlForOBS({
fileName,
mimeType: contentType,
});
const signedUrl = presignedRes.data.signedUrl;
console.log('获取预签名URL:', signedUrl);
// 2. 使用 PUT 上传纯文本
const uploadRes = await fetch(signedUrl, {
method: 'PUT',
headers: {
'Content-Type': contentType,
...presignedRes.data.actualSignedRequestHeaders,
},
body: text, // 直接传字符串!
});
console.log('uploadRes', uploadRes);
// if (!uploadRes.ok) {
// const errText = await uploadRes.text();
// console.error('上传失败:', errText);
// Alert.alert('失败', `状态: ${uploadRes.status}\n${errText}`);
// return;
// }
// console.log('✅ 上传成功!状态:', uploadRes.status);
// // 3. 拼接公开访问链接(确保桶是公共读)
// const publicUrl = `https://my-obs-bucket-demo.obs.cn-north-4.myhuaweicloud.com/${fileName}`;
// console.log('访问地址:', publicUrl);
// Alert.alert('成功', `上传成功!\n访问地址:\n${publicUrl}`);
// // 4. (可选)测试下载
// const downloadRes = await fetch(publicUrl);
// const downloadedText = await downloadRes.text();
// console.log('下载内容:', downloadedText);
// if (downloadedText === text) {
// console.log('🎉 内容一致,OBS 读写正常!');
// } else {
// console.error('❌ 内容不一致!');
// }
} catch (error: any) {
console.error('上传异常:', error);
Alert.alert('错误', error.message || '未知错误');
}
};
export {uploadFile, testTextUpload};
更多推荐
所有评论(0)