对象存储 uni-app 直传实践

这篇档介绍如何不依赖 SDK,使用简单的代码,在 uni-app 直传文件到对象存储(Cloud Object Storage,COS)的存储桶。

相关产品
对象存储 产品页,含版本规格、价格与购买入口。

一、简介

注意:说明:

注意:这篇档内容基于 XML API 的 PostObject 接口和 PutObject 接口。

二、执行过程

1. 在前端选择文件,前端将后缀发送给服务端。

2. 服务端根据后缀,生成带时间的随机 COS 文件路径,并计算对应的 PostObject policy 签名,返回 URL 和签名信息给前端。

3. 前端调用 PostObject 接口或者 PutObject 接口,直传文件到 COS。

三、方案优势

·路径安全:由服务端决定随机的 COS 文件路径,可以有效避免已有文件被覆盖的问题和安全风险。

·兼容多端:使用 uni-app 提供的选文件、上传接口,可以一份代码多端可兼容(Web/小程序/App)。

四、前提条件

1. 登录 COS 控制台 并创建存储桶,得到 Bucket(存储桶名称) 和 Region(地域名称),详情请参见 创建存储桶 文档。

2. 登录 访问管理控制台, 获取你的项目 SecretId 和 SecretKey。

3. 进入刚创建的存储桶详情页面,在安全管理 > 跨域访问 CORS 设置页面,单击添加规则。配置示例如下,详情请参见 设置跨域访问 文档。

五、实践步骤

注意:正式部署前,建议你在服务端加一层对网站本身的权限检验。

六、前端上传

1. 参考 post-policy 示例(PostObject 服务端接口)或 put-sign 示例(PutObject 服务端接口),用于生成随机文件路径、计算签名,并返回给前端。

2. 使用 HBuilderX 默认模板(详见 HBuilderX 页面)创建 uni-app 应用。

3. 选择 文件 > 新建 > 项目,完成创建后,该应用为一个基于 Vue 的项目。

4. 复制以下代码替换 pages/index/index.vue 文件内容,并修改调用的 post-policy 接口链接,将其指向自己的服务端地址(即步骤1的服务端接口)。

PostObject 上传PutObject 上传<template><view class="content"> <button type="default" @click="selectUpload">选择文件上传</button> <image v-if="fileUrl" class="image" :src="fileUrl"></image></view></template><script>export default { data() { return { title: 'Hello', fileUrl: '' }; }, onLoad() { }, methods: { selectUpload() { var vm = this; // 对更多字符编码的 url encode 格式 var camSafeUrlEncode = function (str) { return encodeURIComponent(str) .replace(/!/g, '%21') .replace(/'/g, '%27') .replace(/\\(/g, '%28') .replace(/\\)/g, '%29') .replace(/\\*/g, '%2A'); }; // 获取上传路径、上传凭证 var getUploadInfo = function (extName, callback) { // 传入文件后缀,让后端生成随机的 COS 对象路径,并返回上传域名、PostObject 接口要用的 policy 签名 // 参考服务端示例:https://github.com/tencentyun/cos-demo/tree/main/server/post-policy uni.request({ url: 'http://127.0.0.1:3000/post-policy?ext=' + extName, success: (res) => { // 确认返回格式是否正确 console.log(res); callback && callback(null, res.data); }, fail(err) { callback && callback(err); }, }); }; // 发起上传请求,上传使用 PostObject 接口,使用 policy 签名保护 // 接口文档:https://cloud.tencent.com/document/product/436/14690#.E7.AD.BE.E5.90.8D.E4.BF.9D.E6.8A.A4 var uploadFile = function (opt, callback) { var formData = { key: opt.cosKey, policy: opt.policy, // 这个传 policy 的 base64 字符串 success_action_status: 200, 'q-sign-algorithm': opt.qSignAlgorithm, 'q-ak': opt.qAk, 'q-key-time': opt.qKeyTime, 'q-signature': opt.qSignature, }; // 如果服务端用了临时密钥计算,需要传 x-cos-security-token if (opt.securityToken) formData['x-cos-security-token'] = opt.securityToken; uni.uploadFile({ url: 'https://' + opt.cosHost, //仅为示例,非真实的接口地址 filePath: opt.filePath, name: 'file', formData: formData, success: (res) => { if (![200, 204].includes(res.statusCode)) return callback && callback(res); var fileUrl = 'https://' + opt.cosHost + '/' + camSafeUrlEncode(opt.cosKey).replace(/%2F/g, '/'); callback && callback(null, fileUrl); }, fail(err) { callback && callback(err); }, }); }; // 选择文件 uni.chooseImage({ success: (chooseImageRes) => { var file = chooseImageRes.tempFiles[0]; if (!file) return; // 获取要上传的本地文件路径 var filePath = chooseImageRes.tempFilePaths[0]; // 获取上传的文件后缀,然后后端生成随机 COS 路径地址 var fileName = file.name; var lastIndex = fileName.lastIndexOf('.'); var extName = lastIndex > -1 ? fileName.slice(lastIndex + 1) : ''; // 获取预上传用的域名、路径、凭证 getUploadInfo(extName, function (err, info) { // 确认返回 info 格式是否正确 console.log(info); // 上传文件 info.filePath = filePath; uploadFile(info, function (err, fileUrl) { vm.fileUrl = fileUrl; }); }); } }); }, }}</script><style>.content { padding: 20px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;}.image { margin-top: 20px; margin-left: auto; margin-right: auto;}</style><template><view class="content"> <button type="default" @click="selectUpload">选择文件上传</button> <image v-if="fileUrl" class="image" :src="fileUrl"></image></view></template><script>export default { data() { return { title: 'Hello', fileUrl: '' }; }, onLoad() { }, methods: { selectUpload() { var vm = this; // 对更多字符编码的 url encode 格式 var camSafeUrlEncode = function (str) { return encodeURIComponent(str) .replace(/!/g, '%21') .replace(/'/g, '%27') .replace(/\\(/g, '%28') .replace(/\\)/g, '%29') .replace(/\\*/g, '%2A'); }; // 获取上传路径、上传凭证 var getUploadInfo = function (extName, callback) { // 传入文件后缀,让后端生成随机的 COS 对象路径,并返回 PUT Object 接口要用的签名 // 后端需要返回:cosHost, cosKey, authorization, securityToken(可选) uni.request({ url: 'http://127.0.0.1:3000/put-signature?ext=' + extName, success: (res) => { // 确认返回格式是否正确 console.log(res); callback && callback(null, res.data); }, fail(err) { callback && callback(err); }, }); }; // 读取文件为 ArrayBuffer var readFileAsArrayBuffer = function (filePath, callback) { uni.getFileSystemManager().readFile({ filePath: filePath, success: (res) => { callback && callback(null, res.data); // res.data 是 ArrayBuffer }, fail(err) { callback && callback(err); } }); }; // 发起上传请求,使用 PUT Object 接口 // 接口文档:https://cloud.tencent.com/document/product/436/7749 var uploadFile = function (opt, fileData, callback) { var headers = { 'Authorization': opt.authorization, // 签名信息 'Content-Type': opt.contentType || 'application/octet-stream' }; // 如果服务端用了临时密钥计算,需要传 x-cos-security-token if (opt.securityToken) { headers['x-cos-security-token'] = opt.securityToken; } console.log('上传请求信息:', { url: 'https://' + opt.cosHost + '/' + opt.cosKey, headers: headers }); uni.request({ url: 'https://' + opt.cosHost + '/' + opt.cosKey, method: 'PUT', header: headers, data: fileData, // ArrayBuffer 格式 success: (res) => { console.log('上传响应:', res); if (![200, 204].includes(res.statusCode)) { console.error('上传失败,状态码:', res.statusCode); return callback && callback(res); } var fileUrl = 'https://' + opt.cosHost + '/' + camSafeUrlEncode(opt.cosKey).replace(/%2F/g, '/'); callback && callback(null, fileUrl); }, fail(err) { console.error('上传请求失败:', err); callback && callback(err); }, }); }; // 选择文件 uni.chooseImage({ success: (chooseImageRes) => { var file = chooseImageRes.tempFiles[0]; if (!file) return; // 获取要上传的本地文件路径 var filePath = chooseImageRes.tempFilePaths[0]; // 获取上传的文件后缀,然后后端生成随机 COS 路径地址 var fileName = file.name; var lastIndex = fileName.lastIndexOf('.'); var extName = lastIndex > -1 ? fileName.slice(lastIndex + 1) : ''; // 获取预上传用的域名、路径、签名 getUploadInfo(extName, function (err, info) { if (err) { console.error('获取上传信息失败:', err); return; } // 确认返回 info 格式是否正确 console.log(info); // 读取文件为 ArrayBuffer readFileAsArrayBuffer(filePath, function (err, fileData) { if (err) { console.error('读取文件失败:', err); return; } // 上传文件 uploadFile(info, fileData, function (err, fileUrl) { if (err) { console.error('上传失败:', err); return; } vm.fileUrl = fileUrl; console.log('上传成功:', fileUrl); }); }); }); } }); }, }}</script><style>.content { padding: 20px 0; display: flex; flex-direction: column; align-items: center; justify-content: center;}.image { margin-top: 20px; margin-left: auto; margin-right: auto;}</style>

5. 在 HBuilderX 上,选择运行 > 运行到浏览器 > Chrome,即可在浏览器中选择文件进行上传。

6. 执行效果如下:

·创建项目:


·直传效果:


七、相关文档

上传安全限制

赞 (0)
上一篇 5天前
下一篇 5天前