自己的网站(https://www.gf-app.cn)目前只做了第三方登录。主要是考虑用手机号登录,就得接短信验证码服务。怕被人恶意刷短信,就得做防刷机制,否则有被刷短信费的风险。
首次登录会自动获取用户在支付宝的头像和昵称来自动注册。用户可以通过修改资料的重新上传头像。
茴香豆的“茴”字有五种写法,上传头像的方法,同样有5种。
建议都使用第三方图床(以阿里云OSS为例)。主要是看中了能够自由缩放图片尺寸。这5种方法具体的优缺点评比见后面的表格。
- 直接将图片上传到自己的服务器。
就是图片通过表单提交到后端,后端将图片保存到自己服务器的某个地方后,继续后续逻辑。
难度:☆
推荐度:☆
- 简单,这是最基本的功能实现,一个后端即可完成;
- 所有图片都保存在自己的服务器上,占用了硬盘空间。而硬盘空间是需要花钱买的;
- 图片越多,花费越高!不推荐。
- 先上传再转存图床(使用OSS)
先用方法1实现将图片上传到自己的服务器,再调用阿里云OSS的sdk,将图片转存过去。
难度:☆☆
推荐度:☆☆
- 难度方面,相对方法1,增加了转存的逻辑。因为有现成的sdk供调用,只需要学习oss怎么配置即可。一个后端即可完成;
- 由于图片还是得先上传到自己服务器,依然避免不了占用硬盘的问题。而且为了节省硬盘,还需要写代码清理自己服务器的图片。此外转存带来了图片的二次上传,代码的执行时间会变的更长;
- 在项目的前期研发阶段,可以当做临时方案使用。
- javascript直传
前端直传需要用到安全加密算法的数字签名。这种方法里,签名由前端生成,生成好的签名和图片就可以全部由前端代码发到OSS端了,解放了后端。
难度:☆☆☆(前端)
推荐度:☆☆
- 需要学习如何生成数字签名,一个前端可以搞定;
- 因为是直接上传,所以不会占用服务器硬盘;
- 由于签名需要前端生成,所以敏感性数据比如access_id_key就得写到js代码里了。前端代码几乎是透明的,如果有懂的人,直接把你的密钥拿走干别的了,你还得傻乎乎的给人家续费;
- 有安全风险,不推荐!
- 服务端签名后javascript直传
流程和方法3相似,只是数字签名交给了后端。javascript带着签名将图片上传给OSS。
难度:☆☆☆☆
推荐度:☆☆☆☆☆
- 后端需要生成加密签名,并提供给前端使用,这里有一些难度,不过可以参考阿里云的demo ,需要前后端进行配合;
- 这种方法很好用,但是有一定的业务局限性。比如用户上传了照片,但是不点击保存就把网页关了,那oss空间就白白浪费了。
- 根据自身业务选择,强烈推荐!
- 服务端签名直传后设置回调
流程相对比方法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"]},
}
这份文档就写在这里了,快去试试吧!如果有帮助,请帮我点个赞。如果有不清楚的地方或可以继续优化的地方,欢迎在下方留言讨论哦。
网友评论