需求描述
在使用uni-app开发社区模块时,发布帖子拥有上传图片的功能,uni-app提供上传文件的API——uni.uploadFile。
uploadFile的功能是将本地url图片以post请求的方式上传到资源服务器中,思来想去决定使用阿里云OSS作为资源服务器。
参考:
微信小程序直传实践
该文档罗列了3个步骤:
- 配置白名单
- 提供签名服务
- 使用uni.uploadFile上传资源文件
第一步太基础了就不再介绍。
直接介绍第二步:
1 提供签名服务
在阿里云 OSS 中,资源的访问和操作(上传、下载、删除等)都受到严格的权限控制,默认情况下,只有拥有权限的用户或客户端才能执行这些操作。为了确保安全,OSS 使用签名机制来验证每个请求的合法性。
权限控制:OSS 中的每个存储空间(Bucket)和对象(文件)都可以设置访问权限,例如:
- 私有(Private):默认状态,只有拥有正确权限或签名的人可以访问或修改。
- 公共读私有写(Public Read, Private Write):所有人可以读取资源,但只有有权限的人可以上传或修改。
- 公共读写(Public Read Write):所有人都可以读写资源(不推荐)。
签名机制: 为了确保访问和操作 OSS 资源的安全性,阿里云要求每个访问请求都要有合法的签名,尤其是私有资源。这些签名可以通过两种方式获取:
- 静态凭证:通过 AccessKeyId 和 AccessKeySecret
为每个请求生成签名。这个方式适用于有固定后端应用的场景,但不建议直接在客户端暴露这些凭证。 - 临时授权(STS):为了更加灵活且安全地处理 OSS 的访问和操作,阿里云提供了 STS(安全令牌服务)。通过
STS,你可以生成一个短时间有效的临时签名,客户端使用该签名进行上传或下载操作,而不会暴露敏感的凭证(AccessKeySecret)。
签名的作用: 签名本质上是对请求的加密验证,用来确保:
- 请求的合法性:只有具有正确签名的人才能进行访问和操作。
- 请求的时效性:签名通常会设置一个有效期,超出有效期的请求将被拒绝,确保安全性。
- 最小化权限泄露:通过临时签名,你可以确保客户端只能在有限的时间内对指定的资源进行操作,避免长时间暴露权限。
1.1 签名服务在ECS服务器上的部署
Step1 安装Node.js
本节参考:下载Node.js
# 安装 nvm (Node 版本管理器)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
# 下载并安装 Node.js(可能需要重启终端)
nvm install 20
# 验证环境中是否存在正确的 Node.js 版本
node -v # 应该打印 `v20.18.0`
# 验证环境中是否存在正确的 npm 版本
npm -v # 应该打印 `10.8.2`
如果在执行node -v时报如下错误,通常是因为新版的node v18开始,都需要GLIBC_2.27支持。
node: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by node)
node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by node)
node: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by node)
node: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by node)
node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by node)
node: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by node)
查看一下系统目前的GLIBC版本:
strings /lib64/libc.so.6 | grep GLIBC_
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_2.13
GLIBC_2.14
GLIBC_2.15
GLIBC_2.16
GLIBC_2.17
那就使用17版本的NodeJS就好了。
# 卸载node20
nvm uninstall 20
# 安装node17
nvm install 17
# 验证环境中是否存在正确的 Node.js 版本
node -v # 应该打印 `v17.9.1`
# 验证环境中是否存在正确的 npm 版本
npm -v # 应该打印 `8.11.0`
将node添加到环境变量中:
# 查看node的路径
which node
/root/.nvm/versions/node/v17.9.1/bin/node
# 修改环境变量配置文件
vim ~/.bash_profile
# 添加node路径
PATH=$PATH:$HOME/bin:/root/.nvm/versions/node/v17.9.1/bin
export PATH
Step2 安装OSS SDK
npm install ali-oss --save
Step3 安装express和https
由于微信小程序上传文件的服务器白名单必须以https开头,所以,必须拥有SSL证书,如何获取SSL证书本文不进行探讨。
npm install https
npm install express
npm install fs
然后在/var/www/下创建一个node项目目录
mkdir node-project
cd node-project
node init -y
vim app.js
const express = require('express');
const https = require("https");
const app = express();
const fs = require('fs');
const httpsOption = {
key:fs.readFileSync("/xxxxxxxxxxxxxxxxxxxxxx.key"),
cert: fs.readFileSync("/xxxxxxxxxxxxxxxxxxxxxx.pem")
}
const server = https.createServer(httpsOption, app)
app.get('/', (req, res) => {
res.send('Hello from Express!');
});
server.listen(3000, () => {
console.log('Server running at http://127.0.0.1:3000/');
});
安装pm2模块自动启动nodejs:
npm insatll pm2 -g
cd /var/www/node-project
pm2 start app.js
pm2 startup systemd
pm2 ls
然后在浏览器中输入
https://your_domain:3000/
就可以看到:
Step4 编辑服务器端签名代码
先安装crypto模块:
npm install crypto-js
在/var/www/node-project下创建uploadOssHelper.js文件,代码如下:
const crypto = require("crypto-js");
class MpUploadOssHelper {
constructor(options) {
this.accessKeyId = options.accessKeyId;
this.accessKeySecret = options.accessKeySecret;
// 限制参数的生效时间,单位为小时,默认值为1。
this.timeout = options.timeout || 1;
// 限制上传文件的大小,单位为MB,默认值为10。
this.maxSize = options.maxSize || 10;
}
createUploadParams() {
const policy = this.getPolicyBase64();
const signature = this.signature(policy);
return {
OSSAccessKeyId: this.accessKeyId,
policy: policy,
signature: signature,
};
}
getPolicyBase64() {
let date = new Date();
// 设置policy过期时间。
date.setHours(date.getHours() + this.timeout);
let srcT = date.toISOString();
const policyText = {
expiration: srcT,
conditions: [
// 限制上传文件大小。
["content-length-range", 0, this.maxSize * 1024 * 1024],
],
};
const buffer = Buffer.from(JSON.stringify(policyText));
return buffer.toString("base64");
}
signature(policy) {
return crypto.enc.Base64.stringify(
crypto.HmacSHA1(policy, this.accessKeySecret)
);
}
}
module.exports = MpUploadOssHelper;
使用dotenv模块来加载环境变量到process.env
npm install dotenv
在项目的根目录下创建.env文件(/var/www/node-project/.env),并添加环境变量:
OSS_ACCESS_KEY_ID=your-access-key-id
OSS_ACCESS_KEY_SECRET=your-access-key-secret
关于如何获取AccessKey和AccessKeySecret,请参考:创建AccessKey
同时请确保Bucket的授权策略添加了授权的用户。
然后编辑app.js
require('dotenv').config();
const express = require('express');
const https = require("https");
const app = express();
const fs = require('fs');
const MpUploadOssHelper = require("./uploadOssHelper.js");//导入
const httpsOption = {
key:fs.readFileSync("/xxxxxxxxxxxxxxxxxxx.key"),
cert: fs.readFileSync("/xxxxxxxxxxxxxxx.pem")
}
const server = https.createServer(httpsOption, app)
app.get('/', (req, res) => {
res.send('Hello from Express!');
});
app.get("/getPostObjectParams",(req, res) => {
const mpHelper = new MpUploadOssHelper({
accessKeyId: process.env.OSS_ACCESS_KEY_ID,
accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
// 限制参数的生效时间,单位为小时,默认值为1。
timeout: 1,
// 限制上传文件大小,单位为MB,默认值为10。
maxSize: 10,
});
const params = mpHelper.createUploadParams(); // 生成参数
res.json(params);
});
server.listen(3000, () => {
console.log('Server running at http://127.0.0.1:3000/');
});
然后开启node.js
node app.js
Server running at http://127.0.0.1:3000/
2 使用uni.uploadFile上传资源文件
const host = '<host>';
const signature = '<signatureString>';
const ossAccessKeyId = '<accessKey>';
const policy = '<policyBase64Str>';
const key = '<object name>';
const securityToken = '<x-oss-security-token>';
const filePath = '<filePath>'; // 待上传文件的文件路径。
wx.uploadFile({
url: host,
filePath: filePath,
name: 'file', // 必须填file。
formData: {
key,
policy,
OSSAccessKeyId: ossAccessKeyId,
signature,
// 'x-oss-security-token': securityToken // 使用STS签名时必传。
},
success: (res) => {
if (res.statusCode === 204) {
console.log('上传成功');
}
},
fail: err => {
console.log(err);
}
});