uniapp+node.js 开发小程序上传图片到阿里云OSS

29 篇文章 1 订阅

1. 效果图:

 经过测试在小程序端,H5端都能正常运行。

2. 后端node.js代码

后端代码主要负责签名服务,把签名返回给前端直接上传图片到阿里云OSS 。

官方说明文档 : 微信小程序直传实践

完整代码:

// 阿里云OSS官方文档 https://help.aliyun.com/document_detail/92883.html
// 步骤3:获取签名 -- 服务端签名

// 导入模块
const express = require("express");
const app = express();
const MpUploadOssHelper = require("./js/uploadOssHelper.js");
const OSS = require('ali-oss');

// 设置跨域访问
app.all("*",function(req,res,next){    
    res.header("Access-Control-Allow-Origin","*");  //设置允许跨域的域名,*代表允许任意域名跨域    
    res.header("Access-Control-Allow-Headers","content-type");  //允许的header类型    
    res.header("Access-Control-Allow-Methods","DELETE,PUT,POST,GET,OPTIONS");  //跨域允许的请求方式 
    if (req.method.toLowerCase() == 'options'){
		res.send(200);  //让options尝试请求快速结束
	}else{
		next();
	}        
})

// 声明常量,在阿里云控制台找 https://ram.console.aliyun.com/users
const OSSaccessKeyId = 'xxxxxxxx';
const OSSaccessKeySecret = 'xxxxxxxxx';
const OSSregion = 'oss-cn-xxxxxxxx';
const OSSbucket = 'xxxxxxxx';

// 声明变量
var markerString = null;


/*签名服务*/
app.get("/getParams", (req, res) => {
	const mpHelper = new MpUploadOssHelper({
		// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
		accessKeyId: OSSaccessKeyId,
		accessKeySecret: OSSaccessKeySecret,
		// 限制参数的生效时间,单位为小时,默认值为1。
		timeout: 1,
		// 限制上传文件大小,单位为MB,默认值为10。
		maxSize: 10,
	});

	// 生成参数。
	const params = mpHelper.createUploadParams();
	console.log(params);
	res.json(params);
});


/*请求文件列表*/
app.get("/getList", (req, res) => {	
	const client = new OSS({
		// yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
		region: OSSregion,
		// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
		accessKeyId: OSSaccessKeyId,
		accessKeySecret: OSSaccessKeySecret,
		// yourbucketname填写存储空间名称。
		bucket: OSSbucket
	});	
	
	//分页列举文件
	async function list() {		
		const result = await client.list({
			"marker":markerString, //分页标记
			"max-keys": 16,   //设置按字母排序最多返回前16个文件。			
			"prefix": 'photo/'   //列举文件名中包含前缀photo/的文件。
		});
		
		markerString = result.nextMarker;
		console.log(result);
		
		//提取部分属性组合成新的对象
		let obj = {}
		obj.resList = result.objects  
		obj.nextMarker = result.nextMarker
		
		res.send(obj)	//发送给前端	
	}
	
	list();
});

app.listen(8000);
console.log('服务开启成功,访问地址为 http://127.0.0.1:8000');

3. 前端uniapp代码

主页源代码 index.vue  ,向后端请求签名,拿到签名后向阿里云OSS直传图片,文件不经过后端,上传速度不受后端服务器带宽影响。

// index.vue

<template>
	<view class="content">
		<image class="logo" src="@/static/up.png" @click="selectFile"></image>
		<view class="text-area">
			<text class="title">{{title}}</text>
		</view>
		
		<button type="default"   @click="gotoView" style="margin-top: 50px;" > 浏览云端图片 </button>
	</view>
</template>

<script>
	// 声明全局变量
	var serverUrl = '';  
	var host = 'http://xxxxxxxx.oss-cn-xxxxxxx.aliyuncs.com'; //阿里云OSS Bucket的访问域名
	var ossSignature = '';
	var ossAccessKeyId = '';
	var ossPolicy = '';

	export default {
		data() {
			return {
				title: '选择上传的图片',				
			}
		},

		onLoad() {
			serverUrl = 'https://xxxxxxxxxxxxxxxx.com'; // 后端签名服务地址,自己搭建
			uni.setStorageSync('serverUrl',serverUrl); //写入缓存
			
			this.getParams();
		},

		methods: {
			/* 向后端请求签名 */
			getParams() {
				uni.request({
					url: serverUrl + '/getParams', // 后端签名服务接口,自己搭建
					success: (res) => {
						console.log(res);
						ossSignature = res.data.signature;
						ossAccessKeyId = res.data.OSSAccessKeyId;
						ossPolicy = res.data.policy;						
					},
					fail: (err) => {
						console.log('请求签名失败:',err);
					}
			
				})
			},
			
			/* 选择文件 */
			selectFile() {
				let that = this
				uni.chooseImage({
					count: 9, // 默认最多一次选择9张图
					sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
					sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有

					success: function(res) {
						let tempFilePaths = res.tempFilePaths; //本地临时路径

						for (var i = 0; i < tempFilePaths.length; i++) {
							if (res.tempFiles[i].size > 1024*1024*10) { //上传文件大小限制 10M
								uni.showToast({
									title: '文件大小不能超过10M',
									icon: 'none',
									duration: 3000
								});
								return;
							} else {
								let date = new Date;
								let year = date.getFullYear();
								let month = date.getMonth() + 1;
								
								// 保存到 OSS 的路径和文件名
								let key = "photo/" + year + "-" + month + "/" + Date.parse(new Date()) + parseInt(Math.random() * (100000 - 10000 + 1) + 10000, 10) + ".jpg"
								
								// 调用上传函数								
								that.upload(key,tempFilePaths[i])
							}
						}
					}
				})
			},
			
			/* 上传文件 */
			upload(key,filePath) {
				uni.showLoading({
					title:'上传中...',
					icon: 'loading',
					mask:true					
				});
				
				// 阿里云OSS官方文档 https://help.aliyun.com/document_detail/92883.html
				wx.uploadFile({
					url: host,  // oss服务地址,例如:http://xxxxxx.oss-cn-beijing.aliyuncs.com
					filePath: filePath, // 填写待上传文件的本地完整路径,例如 D:\example.txt
					name: 'file', // 必须填file。
					formData: {
						'key': key, //设置文件上传至OSS后的文件路径。例如,您需要将myphoto.jpg上传至test文件夹下,则填写test/myphoto.jpg。
						'policy': ossPolicy , // 后端签名服务返回
						'OSSAccessKeyId': ossAccessKeyId, // 后端签名服务返回
						'signature': ossSignature, // 后端签名服务返回
						// 'x-oss-security-token': securityToken // 使用STS签名时必传。
					},
					success: (res) => {
						console.log(res);
						
						if (res.statusCode === 204) {
							console.log('上传成功');
							console.log('访问地址:', host + '/' + key);
						}
					},
					fail: (err) => {
						console.log('上传失败:' ,err);
					},
					complete:() =>{
						uni.hideLoading()
					}
				})				
			},
			
			gotoView(){				
				uni.navigateTo({
					url:'../imageView/imageView'
				})
			}
		}
	}
</script>

<style>
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}

	.logo {
		height: 200rpx;
		width: 200rpx;
		margin-top: 200rpx;
		margin-left: auto;
		margin-right: auto;
		margin-bottom: 50rpx;
	}

	.text-area {
		display: flex;
		justify-content: center;
	}

	.title {
		font-size: 36rpx;
		color: #8f8f94;
	}
</style>

查看图片页面源代码 imageView.vue  ,此处用的是瀑布流形式展示图片,向后端分页请求数据,提高前端页面图片加载速度。

// imageView.vue  
/* 源码下载地址:
链接:https://pan.baidu.com/s/1AVB71AjEX06wpc4wbcV_tQ?pwd=l9zp
提取码:l9zp */

<template>
	<view class="free-panel-title">
		<view class="free-WaterfallFlow">
		  <block>
			<view class="flex-wrap" v-for="(item,index) in imgList" :key="index" v-if="index % 2 != 0">
				<image mode="widthFix" :src="item.url" :data-src="item.url" @click="clickimg" ></image>
			</view>
		  </block>
		  <block>
			<view class="flex-wrap" v-for="(item2,index2) in imgList" :key="index2" v-if="index2 % 2 == 0">
				<image mode="widthFix" :src="item2.url" :data-src="item2.url" @click="clickimg" ></image>
			</view>
		  </block>
		</view>
		<!--返回顶部-->
		<view class="top" :style="{'display':(flag===true? 'block':'none')}">
			<image class="topc" @click="top" src="../../static/top.png" ></image>
		</view>
	</view>
</template>

<script>
	// 声明全局变量
	var serverUrl = '';  // 后端签名服务地址,自己搭建
	
	export default {
		data() {
			return {				
				imgList: [],				
				flag: false,
				hasMore:true ,
				pageIndex: 1 ,
			}
		},
		
		onLoad() {
			serverUrl = uni.getStorageSync('serverUrl') //从缓存读取
			this.getData()
		},
		
		// 上拉触底事件
		onReachBottom:function( ) { 
			if (this.hasMore) {
				this.getData()  //调用函数
				setTimeout(() => {
					uni.stopPullDownRefresh();
				}, 1000);
				
			} else {
				uni.showToast({
				  title: '没有更多数据了!',
				  icon:'none'
				})
			}      
		},
		
		onPageScroll(e) { //根据距离顶部距离是否显示回到顶部按钮
			if(e.scrollTop>600){ //当距离大于600时显示回到顶部按钮
				this.flag=true
			}else{ //当距离小于600时隐藏回到顶部按钮
				this.flag=false
			}
		},
		
		methods: {
			getData() {
				if(this.hasMore) {
					uni.showLoading({
						title: '加载中...'
					});					

					uni.request({					
						url:serverUrl + '/getList',  //后端接口地址
						data:{ },
						success:(res) => {
							console.log(res)
							this.imgList.push(...res.data.resList)  //云端返回的数据追加到数组
							
							if (res.data.nextMarker==null) { //判断云端是否有更多数据
								this.hasMore = false
							}else{
								this.hasMore = true
							}						
						},
						fail:()=> {
							console.log('请求后端接口失败')
						},
						complete:() => {
							uni.hideLoading()
						}	
					})
					
				} else {
					uni.showToast({
						title:'没有更多数据了',
						icon:'none'
					})
				}
			},
			
			// 图片预览
			clickimg(event) {
				var imgurl = event.currentTarget.dataset.src
				var currentUrl = event.currentTarget.dataset.src   //获取点击图片的地址, **对应<template>里面的 :data-src="item.src"					
				uni.previewImage({       
					urls: [imgurl],    //预览的全部图片地址,这个参数类型必须是数组 					
					current: currentUrl, //当前显示图片的地址					
				})  
			},
			
			//回到顶部
			top() { 
				uni.pageScrollTo({
					scrollTop: 0,
					duration: 300
				});
			}
		}
	}
</script>

<style>
	.free-WaterfallFlow{
		width:96%;
		column-count:2; /* 分隔的列数 */
	}
	.free-WaterfallFlow .flex-wrap{
		display: inline-block;
		width:98%;
		margin-left:3%;
		margin-bottom:3%;		
		padding:2%;
		padding-top:5%;	
		border:0px solid #cc22b0; /* 边框 */
		box-shadow: 0 2px 2px rgba(34, 25, 25, 0.4); /* 框阴影 */
		text-align: center; /* 框内元素居中对齐 */
	}
	.flex-wrap image{
		width:95%;
		margin:0 auto;
	}
	.flex-wrap view:nth-child(2){
		font-size:15px;
		padding:2% 0;
		color:#717171;
	}
	.flex-wrap view:nth-child(3){
		font-size:13px;
		padding:2% 0;
		color:#aaa;
		text-align: right;
	}
	
	/* 回到顶部 */
	.top {
		position: relative;
		display: none; /* 先将元素隐藏 */
	} 
	.topc {
		height: 30px; 
		width: 30px;
		position: fixed;
		right: 5px;
		top: 80%;
	}
</style>

4. 碰到的问题及解决方法

主要的问题是前端遇到阿里云返回403错误 。 

按官方的排查方法解决 :访问OSS时出现403状态码的排查方法

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值