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状态码的排查方法