Bootstrap

微信小程序使用腾讯云COS对象存储保存图片和文件

示例效果图:

1、开通COS对象存储服务

腾讯云COS官网地址:https://cloud.tencent.com/product/cos

PS:建议选择使用七牛云,七牛云是完全免费的;腾讯云COS有半年免费50G的存储服务,但腾讯云的流量是收费的,每天都给你发扣费短信,真是烦死人 ;  阿里云的OSS或者华为云的OBS都要收费(不推荐)。

2、创建密钥

 

自定义域名的教程,看官方文档:https://cloud.tencent.com/document/product/436/6252

3、thinkphp后端:计算腾讯云COS对象存储签名  

特别说明:由于签名计算放在前端会暴露 SecretId 和 SecretKey, 所以我们把签名计算过程放在后端实现,前端通过 ajax 向后端获取签名结果,正式部署时请在后端加一层自己网站本身的权限检验。

thinkphp控制器controller中的代码:

<?phpnamespace app\index\controller;use think\Controller;use think\Request;/*引入[腾讯云](https://l.gushuji.site/tencent)cos类库(extend/sts.php)*/import('sts', EXTEND_PATH); class Car extends Controller{        /*     * 获取[腾讯云](https://l.gushuji.site/tencent)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}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;