示例效果图:
1、开通COS对象存储服务
PS:建议选择使用七牛云,七牛云是完全免费的;腾讯云COS有半年免费50G的存储服务,但腾讯云的流量是收费的,每天都给你发扣费短信,真是烦死人 ; 阿里云的OSS或者华为云的OBS都要收费(不推荐)。
2、创建密钥
3、thinkphp后端:计算腾讯云COS对象存储签名
特别说明:由于签名计算放在前端会暴露 SecretId 和 SecretKey, 所以我们把签名计算过程放在后端实现,前端通过 ajax 向后端获取签名结果,正式部署时请在后端加一层自己网站本身的权限检验。
thinkphp控制器controller中的代码:
<?php
namespace app\index\controller;
use think\Controller;
use think\Request;
/*引入腾讯云cos类库(extend/sts.php)*/
import('sts', EXTEND_PATH);
class Car extends Controller{
/*
* 获取腾讯云COS对象存储签名
* 官网:https://cloud.tencent.com/product/cos
*/
public function getSts(){
$sts = new \STS();
// 配置参数
$config = array(
'url' => 'https://sts.tencentcloudapi.com/',
'domain' => 'sts.tencentcloudapi.com',
'proxy' => '',
'secretId' => '你cos密钥中的secretId', // 固定密钥
'secretKey' => '你cos密钥中的secretKey', // 固定密钥
'bucket' => 'myfaka-1256433534',//你的存储桶名称bucket
'region' => 'ap-guangzhou', // bucket所在地域
'durationSeconds' => 1800, // 密钥有效期
'allowPrefix' => '*', // 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的目录,例子:* 或者 a/* 或者 a.jpg
// 密钥的权限列表。简单上传需要以下的权限,其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
'allowActions' => array (
//上传权限
'name/cos:PutObject',
'name/cos:PostObject',
//下载权限
"name/cos:GetObject",
//查询权限
"name/cos:GetBucket",
"name/cos:HeadObject",
//删除权限
"name/cos:DeleteObject",
)
);
// 获取临时密钥,计算签名
$tempKeys = $sts->getTempKeys($config);
// 返回数据给前端
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: http://127.0.0.1'); // 这里修改允许跨域访问的网站
header('Access-Control-Allow-Headers: origin,accept,content-type');
echo json_encode($tempKeys);
}
}
?>
extend/sts.php的代码:(这是官方提供的类)
<?php
/**
* 代码出处:
* https://github.com/tencentyun/qcloud-cos-sts-sdk
*/
class STS{
// 临时密钥计算样例
function _hex2bin($data) {
$len = strlen($data);
return pack("H" . $len, $data);
}
// obj 转 query string
function json2str($obj, $notEncode = false) {
ksort($obj);
$arr = array();
if(!is_array($obj)){
throw new Exception($obj + " must be a array");
}
foreach ($obj as $key => $val) {
array_push($arr, $key . '=' . ($notEncode ? $val : rawurlencode($val)));
}
return join('&', $arr);
}
// 计算临时密钥用的签名
function getSignature($opt, $key, $method, $config) {
$formatString = $method . $config['domain'] . '/?' . $this->json2str($opt, 1);
$sign = hash_hmac('sha1', $formatString, $key);
$sign = base64_encode($this->_hex2bin($sign));
return $sign;
}
// v2接口的key首字母小写,v3改成大写,此处做了向下兼容
function backwardCompat($result) {
if(!is_array($result)){
throw new Exception($result + " must be a array");
}
$compat = array();
foreach ($result as $key => $value) {
if(is_array($value)) {
$compat[lcfirst($key)] = $this->backwardCompat($value);
} elseif ($key == 'Token') {
$compat['sessionToken'] = $value;
} else {
$compat[lcfirst($key)] = $value;
}
}
return $compat;
}
// 获取临时密钥
function getTempKeys($config) {
if(array_key_exists('bucket', $config)){
$ShortBucketName = substr($config['bucket'],0, strripos($config['bucket'], '-'));
$AppId = substr($config['bucket'], 1 + strripos($config['bucket'], '-'));
}
if(array_key_exists('policy', $config)){
$policy = $config['policy'];
}else{
$policy = array(
'version'=> '2.0',
'statement'=> array(
array(
'action'=> $config['allowActions'],
'effect'=> 'allow',
'principal'=> array('qcs'=> array('*')),
'resource'=> array(
'qcs::cos:' . $config['region'] . ':uid/' . $AppId . ':prefix//' . $AppId . '/' . $ShortBucketName . '/' . $config['allowPrefix']
)
)
)
);
}
$policyStr = str_replace('\\/', '/', json_encode($policy));
$Action = 'GetFederationToken';
$Nonce = rand(10000, 20000);
$Timestamp = time();
$Method = 'POST';
$params = array(
'SecretId'=> $config['secretId'],
'Timestamp'=> $Timestamp,
'Nonce'=> $Nonce,
'Action'=> $Action,
'DurationSeconds'=> $config['durationSeconds'],
'Version'=>'2018-08-13',
'Name'=> 'cos',
'Region'=> 'ap-guangzhou',
'Policy'=> urlencode($policyStr)
);
$params['Signature'] = $this->getSignature($params, $config['secretKey'], $Method, $config);
$url = $config['url'];
$ch = curl_init($url);
if(array_key_exists('proxy', $config)){
$config['proxy'] && curl_setopt($ch, CURLOPT_PROXY, $config['proxy']);
}
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,0);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->json2str($params));
$result = curl_exec($ch);
if(curl_errno($ch)) $result = curl_error($ch);
curl_close($ch);
$result = json_decode($result, 1);
if (isset($result['Response'])) {
$result = $result['Response'];
$result['startTime'] = $result['ExpiredTime'] - $config['durationSeconds'];
}
$result = $this->backwardCompat($result);
return $result;
}
// get policy
function getPolicy($scopes){
if (!is_array($scopes)){
return null;
}
$statements = array();
for($i=0, $counts=count($scopes); $i < $counts; $i++){
$actions=array();
$resources = array();
array_push($actions, $scopes[$i]->get_action());
array_push($resources, $scopes[$i]->get_resource());
$principal = array(
'qcs' => array('*')
);
$statement = array(
'actions' => $actions,
'effect' => 'allow',
'principal' => $principal,
'resource' => $resources
);
array_push($statements, $statement);
}
$policy = array(
'version' => '2.0',
'statement' => $statements
);
return $policy;
}
}
class Scope{
var $action;
var $bucket;
var $region;
var $resourcePrefix;
function __construct($action, $bucket, $region, $resourcePrefix){
$this->action = $action;
$this->bucket = $bucket;
$this->region = $region;
$this->resourcePrefix = $resourcePrefix;
}
function get_action(){
return $this->action;
}
function get_resource(){
$index = strripos($this->bucket, '-');
$bucketName = substr($this->bucket, 0, $index);
$appid = substr($this->bucket, $index + 1);
if(!(strpos($this->resourcePrefix, '/') === 0)){
$this->resourcePrefix = '/' . $this->resourcePrefix;
}
return 'qcs::cos:' . $this->region . ':uid/' . $appid . ':prefix//' . $appid . '/' . $bucketName . $this->resourcePrefix;
}
}
?>
4、下载cos-wx-sdk-v5.js
下载地址:https://github.com/tencentyun/cos-wx-sdk-v5/blob/master/demo/lib/cos-wx-sdk-v5.js
5、微信小程序创建config.js,用于保存对象存储参数,代码如下:
module.exports = {
stsUrl: 'https://后端网址/car/getSts.html',//后端获取签名
Bucket: 'myfaka-1256433534',//存储桶名称
Region: 'ap-guangzhou',//所属地域
};
把cos-wx-sdk-v5.js和config.js都放到项目里:
6、index.wxml代码:
<!--图片上传-->
<view class="container">
<van-uploader file-list="{{ fileList }}" upload-text="添加图片" bind:after-read="afterRead" bind:delete="delFile"multiple="{{true}}" />
</view>
特别说明:这里使用的是vant-weapp的文件上传组件,vant框架地址:https://github.com/youzan/vant-weapp
7、index.js代码:
//获取应用实例
const app = getApp()
var COS = require('./cos-wx-sdk-v5')
var config = require('./config');
var toastMsg = '';
//初始化COS对象
var cos = new COS({
// 获取签名
getAuthorization: function(options, callback) {
wx.request({
url: config.stsUrl, // 服务端获取签名
dataType: 'json',
success: function(result) {
var data = result.data;
var credentials = data.credentials;
callback({
TmpSecretId: credentials.tmpSecretId,
TmpSecretKey: credentials.tmpSecretKey,
XCosSecurityToken: credentials.sessionToken,
ExpiredTime: data.expiredTime,
});
}
});
}
});
Page({
/**
* 页面的初始数据
*/
data: {
fileList: [],
date: ''
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
//获取时间,作为图片文件夹名,如20191207
this.setData({
date: app.globalData.util.dateFormat(new Date(), "YMD")
});
//清除缓存
//wx.removeStorageSync('fileList');
//获取缓存中的地址
this.updateData();
},
afterRead(event) {
toastMsg = "上传";
var that = this;
// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
/* 单个上传 */
/*
const { file } = event.detail;
var filePath = file.path;
var filename = new Date().getTime() + '.'+ filePath.substr(filePath.lastIndexOf('.') + 1);
//文件相对路径名
var relativePath = 'upload/' + that.data.date + '/' + filename;
cos.postObject({
Bucket: config.Bucket,
Region: config.Region,
Key: relativePath,
FilePath: filePath,
onProgress: function (info) {
}
}, requestCallback);
//添加到预览中
var img = {
id: i,
url: app.globalData.cosUrl + relativePath,
name: filename
}
//读取缓存
let list = wx.getStorageSync('fileList');
if (list) {
list.push(img);
} else {
list = [img];
}
//存入缓存
wx.setStorageSync('fileList', list);
//延迟更新数据
setTimeout(function () {
that.updateData();
}, 5000);
*/
/* 批量上传 */
var files = event.detail.file; //数组
for (var i = 0; i < files.length; i++) {
var filePath = files[i].path;
var filename = new Date().getTime() + '.' + filePath.substr(filePath.lastIndexOf('.') + 1);
//文件相对路径名
var relativePath = 'upload/' + that.data.date + '/' + filename;
cos.postObject({
Bucket: config.Bucket,
Region: config.Region,
Key: relativePath,
FilePath: filePath,
onProgress: function(info) {
}
}, requestCallback);
//添加到预览中
var img = {
id: i,
url: app.globalData.cosUrl + relativePath,
name: filename
}
//读取缓存
let list = wx.getStorageSync('fileList');
if (list) {
list.push(img);
} else {
list = [img];
}
//存入缓存
wx.setStorageSync('fileList', list);
}
//延迟更新数据
setTimeout(function () {
that.updateData();
}, 5000);
},
delFile(event) {
toastMsg = "删除";
var that = this;
wx.showModal({
title: '提示',
content: '确定要删除这张图片吗?',
success(res) {
if (res.confirm) {
var index = event.detail.index;
//读取缓存
let list = wx.getStorageSync('fileList');
var filename = list[index].name;
//更新fileList中的数据
for (let i = 0; i < list.length; i++) {
//如果item是选中的话,就删除它。
if (filename == list[i].name) {
// 删除对应的索引
list.splice(i, 1);
break;
}
}
//更新缓存
wx.setStorageSync('fileList', list);
//更新数据
that.updateData();
//删除cos对象存储中的图片
cos.deleteObject({
Bucket: config.Bucket,
Region: config.Region,
Key: 'upload/' + that.data.date + '/' + filename,
}, requestCallback);
} else if (res.cancel) {
//console.log('用户点击取消')
}
}
})
},
//更新数据
updateData() {
this.setData({
fileList: wx.getStorageSync('fileList')
});
},
})
// 回调函数
var requestCallback = function(err, data) {
//console.log(err || data);
if (err && err.error) {
wx.showModal({
title: '返回错误',
content: '请求失败:' + (err.error.Message || err.error) + ';状态码:' + err.statusCode,
showCancel: false
});
} else if (err) {
wx.showModal({
title: '返回错误',
content: '请求出错:' + err + ';状态码:' + err.statusCode,
showCancel: false
});
} else {
wx.showToast({
title: toastMsg + '成功',
icon: 'success',
duration: 3000
});
}
};
util.js时间格式化的代码:
const formatTime = date => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}
const formatNumber = n => {
n = n.toString()
return n[1] ? n : '0' + n
}
/**
* 时间戳转化为年 月 日 时 分 秒
* number: 传入时间戳
* format:返回格式,支持自定义,但参数必须与formateArr里保持一致
*/
function dateFormat(number, format) {
var formateArr = ['Y', 'M', 'D', 'h', 'm', 's'];
var returnArr = [];
var date = new Date(number);
returnArr.push(date.getFullYear());
returnArr.push(formatNumber(date.getMonth() + 1));
returnArr.push(formatNumber(date.getDate()));
returnArr.push(formatNumber(date.getHours()));
returnArr.push(formatNumber(date.getMinutes()));
returnArr.push(formatNumber(date.getSeconds()));
for (var i in returnArr) {
format = format.replace(formateArr[i], returnArr[i]);
}
return format;
}
module.exports = {
formatTime: formatTime,
dateFormat: dateFormat
}