美文网首页程序员
通俗易懂,轻松搞定OSS的签名直传头像功能

通俗易懂,轻松搞定OSS的签名直传头像功能

作者: 玩物励志老乐 | 来源:发表于2020-07-19 12:54 被阅读0次

自己的网站(https://www.gf-app.cn)目前只做了第三方登录。主要是考虑用手机号登录,就得接短信验证码服务。怕被人恶意刷短信,就得做防刷机制,否则有被刷短信费的风险。
首次登录会自动获取用户在支付宝的头像和昵称来自动注册。用户可以通过修改资料的重新上传头像。

茴香豆的“茴”字有五种写法,上传头像的方法,同样有5种。

建议都使用第三方图床(以阿里云OSS为例)。主要是看中了能够自由缩放图片尺寸。这5种方法具体的优缺点评比见后面的表格。

  1. 直接将图片上传到自己的服务器。

就是图片通过表单提交到后端,后端将图片保存到自己服务器的某个地方后,继续后续逻辑。
难度:☆
推荐度:☆

  • 简单,这是最基本的功能实现,一个后端即可完成;
  • 所有图片都保存在自己的服务器上,占用了硬盘空间。而硬盘空间是需要花钱买的;
  • 图片越多,花费越高!不推荐。
  1. 先上传再转存图床(使用OSS)

先用方法1实现将图片上传到自己的服务器,再调用阿里云OSS的sdk,将图片转存过去。
难度:☆☆
推荐度:☆☆

  • 难度方面,相对方法1,增加了转存的逻辑。因为有现成的sdk供调用,只需要学习oss怎么配置即可。一个后端即可完成;
  • 由于图片还是得先上传到自己服务器,依然避免不了占用硬盘的问题。而且为了节省硬盘,还需要写代码清理自己服务器的图片。此外转存带来了图片的二次上传,代码的执行时间会变的更长;
  • 在项目的前期研发阶段,可以当做临时方案使用。
  1. javascript直传

前端直传需要用到安全加密算法的数字签名。这种方法里,签名由前端生成,生成好的签名和图片就可以全部由前端代码发到OSS端了,解放了后端。
难度:☆☆☆(前端)
推荐度:☆☆

  • 需要学习如何生成数字签名,一个前端可以搞定;
  • 因为是直接上传,所以不会占用服务器硬盘;
  • 由于签名需要前端生成,所以敏感性数据比如access_id_key就得写到js代码里了。前端代码几乎是透明的,如果有懂的人,直接把你的密钥拿走干别的了,你还得傻乎乎的给人家续费;
  • 有安全风险,不推荐!
  1. 服务端签名后javascript直传

流程和方法3相似,只是数字签名交给了后端。javascript带着签名将图片上传给OSS。
难度:☆☆☆☆
推荐度:☆☆☆☆☆

  • 后端需要生成加密签名,并提供给前端使用,这里有一些难度,不过可以参考阿里云的demo ,需要前后端进行配合;
  • 这种方法很好用,但是有一定的业务局限性。比如用户上传了照片,但是不点击保存就把网页关了,那oss空间就白白浪费了。
  • 根据自身业务选择,强烈推荐!
  1. 服务端签名直传后设置回调

流程相对比方法4,增加了回调逻辑。后端先生成签名,js带着签名上传图片之后,阿里云会主动调用你提供的接口完成后续操作。
难度:☆☆☆☆☆
推荐度:☆☆☆☆

  • 和方法4相比,几乎没有缺点,也不怕用户中途关闭网页,没有业务局限性。
  • 生成签名时需要增加回调地址和回传参数。
  • 虽然有demo参考,但回调地址必须在公网环境下才能调试,不太方便;
  • 因为不好调试,比较推荐。

优缺点对照表

图片上传对照.png

编码部分

在我这次的修改头像逻辑中,我选择了方法4。如果用户一次只选择一张图片,那么其实可以用选择好图片立即上传的方法,不给用户不点“上传”按钮的机会。因为用户是最懒的,不要妄图教育用户,能让他点一下,就不应该让他点第二下

阿里云提供的服务器签名直传的demo是这样的,需要让人点两下,这不符合我的设计。


oss的上传demo.png

所以我们需要对这个demo里的代码进行修改,以实现我们一次点击完成上传的需求。
这个js的demo,是基于plupload-2.1.2版本。我们需要重新定义FilesAdded事件的处理逻辑。下载了阿里云的demo后,打开upload.js,可以看见这样的代码:

FilesAdded: function(up, files) {
            plupload.each(files, function(file) {
                document.getElementById('ossfile').innerHTML += '<div id="' + file.id + '">' + file.name + ' (' + plupload.formatSize(file.size) + ')<b></b>'
                +'<div class="progress"><div class="progress-bar" style="width: 0%"></div></div>'
                +'</div>';
            });
        },

这个FilesAdded事件,就是当plupload组件拿到要上传的图片之后触发的。阿里云的demo是显示你选择的图片名和文件大小。而我的逻辑则需要直接触发上传。所以我的代码是这样写的:

// 添加文件
uploader.bind("FilesAdded", function (up, files) {
    set_upload_param(up);
    return false;
});

这样一来,我的上传体验就变成了这样:


我的上传.png

操作起来是这样的:


uxdf0-75bsm.gif

怎么样,体验一下子就好多了吧!

附上完整的代码:
前端javascript

var oss_token = {{ oss_token }}
// 上传签名,这里直接输出在页面了。建议头像上传之后自动刷新一下页面。这个token只能用一次。当然你也可以使用同步ajax去服务端获取。
// console.log(oss_token);
// console.log(current_uid);

// 设置上传参数
function set_upload_param(up) {
    // 获取上传签名
    obj = oss_token;
    host = obj['host'];
    policyBase64 = obj['policy'];
    accessid = obj['accessid'];
    signature = obj['signature'];
    expire = parseInt(obj['expire']);
    callbackbody = obj['callback'];
    key = obj['dir'] + "/" + current_uid;  //头像的地址就用user_id来当做key了,可以通过拼接拿到地址,减少读库。
    timestamp = Date.parse(new Date()) / 1000;

    new_multipart_params = {
        'key': key,
        'policy': policyBase64,
        'OSSAccessKeyId': accessid,
        'success_action_status': '200', //让服务端返回200,不然,默认会返回204
        // 'callback' : callbackbody,
        'signature': signature,
    };
    console.log(new_multipart_params);
    up.setOption({
        'url': host,
        'multipart_params': new_multipart_params
    });

    up.start();
}

// 获得头像路径
function get_avatar_path() {
    obj = oss_token;
    host = obj['host'];
    key = obj['dir'] + "/" + current_uid;
    timestamp = Date.parse(new Date()) / 1000;
    return host + "/" + key + "?v=" + timestamp;  //通过修改的时间戳作为附加参数,可以防止浏览器缓存或cdn导致图片不更新
}

// 实例化上传组件
var uploader = new plupload.Uploader({
    runtimes : 'html5,flash,silverlight,html4',
    browse_button : 'selectfiles',   //选择文件按钮的元素ID
    flash_swf_url : 'lib/plupload-2.1.2/js/Moxie.swf',
    silverlight_xap_url : 'lib/plupload-2.1.2/js/Moxie.xap',
    url : 'http://oss.aliyuncs.com',

    filters: {
        mime_types: [ //只允许上传图片
            {title: "Image files", extensions: "jpg,jpeg,gif,png"},
        ],
        max_file_size: '5mb', //最大只能上传5mb的文件
        prevent_duplicates: true //不允许选取重复文件
    },
});
uploader.init();

/** 绑定方法 **/
// 添加文件
uploader.bind("FilesAdded", function (up, files) {
    set_upload_param(up);   //组装签名,执行上传
    return false;
});
//上传成功
uploader.bind("FileUploaded", function (up, file, info) {
    if (info.status == 200) {
        // 将新头像地址保存到数据库
    } else {
        errorMsg("上传失败");
    }
});
//弹出错误提示
function errorMsg(msg) {
    var tips = $('#topTips_avatar');
    tips.text(msg);
    tips.fadeIn(100);
    setTimeout(function () {
        tips.fadeOut(100);
    }, 2000);
}

相对完整的python后端代码

# manage.py
# 获取直传token
    def get_token(self, bucket_name, prefix=""):
        now = int(time.time())
        expire_syncpoint = now + self.expire_time
        # expire_syncpoint = 1612345678
        expire = OssTokenManager.get_iso_8601(expire_syncpoint)

        policy_dict = {}
        policy_dict['expiration'] = expire
        condition_array = []
        array_item = []
        array_item.append('starts-with')
        array_item.append('$key')
        array_item.append(prefix)
        condition_array.append(array_item)
        policy_dict['conditions'] = condition_array
        policy = json.dumps(policy_dict).strip()
        policy_encode = base64.b64encode(policy.encode())
        h = hmac.new(OSS_ACCESS_KEY_SECRET.encode(), policy_encode, sha)
        sign_result = base64.encodebytes(h.digest()).strip()

        token_dict = {}
        token_dict['accessid'] = OSS_ACCESS_KEY_ID
        token_dict['host'] = "http://%s" % self.get_domain(bucket_name)
        token_dict['policy'] = policy_encode.decode()
        token_dict['signature'] = sign_result.decode()
        token_dict['expire'] = expire_syncpoint
        token_dict['dir'] = prefix
        # token_dict['callback'] = base64_callback_body.decode()
        result = json.dumps(token_dict)
        return result


    def get_domain(self, bucket_name):
        # print(bucket_name)
        if bucket_name in OSS_BUCKETNAME:
            return OSS_BUCKETNAME[bucket_name]["domain"]
        else:
            return ""
# config.py
# oss配置

OSS_ACCESS_KEY_ID = 'abcd1234'  //改成你自己的
OSS_ACCESS_KEY_SECRET = '1234abcd'    //改成你自己的
OSS_END_POINT = 'http://oss-cn-beijing.aliyuncs.com'    //改成你自己的

# bucket设置 {类别/用途:{bucket:BUCKET, domain:域名, style:[STYLE]}
OSS_BUCKETNAME = {
    # 头像
    "avatar": {"bucket": 'xx-avatar', "domain": "xx-avatar.oss-cn-beijing.aliyuncs.com",
               "style": ["avatar_middle_w80h80"]},
}

这份文档就写在这里了,快去试试吧!如果有帮助,请帮我点个赞。如果有不清楚的地方或可以继续优化的地方,欢迎在下方留言讨论哦。

相关文章

网友评论

    本文标题:通俗易懂,轻松搞定OSS的签名直传头像功能

    本文链接:https://www.haomeiwen.com/subject/fisfkktx.html