原文链接:图床——基于七牛JS-SDK和KVDB
上传
服务端获取token
# -*- coding: utf-8 -*-
__author__ = 'Sky'
import qiniu
from datetime import datetime
import config
def get_token():
q = qiniu.Auth(config.QINIU_ACCESS_KEY, config.QINIU_SECRET_KEY)
# print str(datetime.now())
token = q.upload_token(config.PIC_BUCKET)
return token
JS上传到七牛
详细使用可参考七牛JS-SDK
var uploader = Qiniu.uploader({
runtimes: 'html5,flash,html4',
browse_button: 'pickfiles',
container: 'container',
drop_element: 'container',
max_file_size: '100mb',
filters: {
mime_types: [
{ title: "图片文件", extensions: "jpg,gif,png,bmp" }
],
prevent_duplicates: true //不允许队列中存在重复文件
},
flash_swf_url: '/static/js/plupload/Moxie.swf',
dragdrop: true,
chunk_size: '4mb',
uptoken_url: $('#uptoken_url').val(),
domain: $('#domain').val(),
auto_start: true,
init: {
'FilesAdded': function(up, files) {
$('table').show();
$('#success').hide();
plupload.each(files, function(file) {
var progress = new FileProgress(file, 'fsUploadProgress');
progress.setStatus("等待...");
});
},
'BeforeUpload': function(up, file) {
var progress = new FileProgress(file, 'fsUploadProgress');
var chunk_size = plupload.parseSize(this.getOption('chunk_size'));
if (up.runtime === 'html5' && chunk_size) {
progress.setChunkProgess(chunk_size);
}
},
'UploadProgress': function(up, file) {
var progress = new FileProgress(file, 'fsUploadProgress');
var chunk_size = plupload.parseSize(this.getOption('chunk_size'));
progress.setProgress(file.percent + "%", up.total.bytesPerSec, chunk_size);
},
'UploadComplete': function() {
$('#success').show();
},
'FileUploaded': function(up, file, info) {
var progress = new FileProgress(file, 'fsUploadProgress');
progress.setComplete(up, info);
var res = $.parseJSON(info);
// 请求后台存储
var domain = up.getOption('domain');
//url = domain + encodeURI(res.key);
var link = domain + res.key;
var key = res.key;
$.ajax({
url:"/upQiniu",
type: "POST",
data: { key: key, url: link}
})
.done(function(data){
if(data === 'error') {
alert('上传失败');
location.reload(true);
}
});
},
'Error': function(up, err, errTip) {
$('table').show();
var progress = new FileProgress(err.file, 'fsUploadProgress');
progress.setError();
progress.setStatus(errTip);
}
,
'Key': function(up, file) {
//当前时间戳
var key = new Date();
return key.getTime()
}
}
});
保存信息到KVDB
- KVDB封装,方便使用
为什么通过字典生成字符串存储而不直接存储json?为什么要使用\x1e
和\x1f
作为连接符?
# -*- coding: utf-8 -*-
__author__ = 'Sky'
import sae
import json
import sae.kvdb
class KvdbStorage():
# 初始化kvdb
def __init__(self):
self.kv = sae.kvdb.KVClient()
# 获取value
def get_value(self, key):
return self.kv.get(key)
# 获取dict_value
def get(self, key):
string_value = self.kv.get(key)
if string_value is None:
return None
return decode_dict(string_value)
# 设置value
def set_value(self, key, value):
self.kv.set(key, value)
# 设置dict_value
def set(self, key, dict_value):
string_value = encode_dict(dict_value)
self.kv.set(key, string_value)
# 批量获取key
def getkeys_by_prefix(self, prefix, limit=100, marker=None):
return list(self.kv.getkeys_by_prefix(prefix, limit=limit, marker=marker))
# 批量获取key/value
def get_by_prefix(self, prefix, limit=100, marker=None):
return self.kv.get_by_prefix(prefix, limit=limit, marker=marker)
# 删除key
def delete(self, key):
self.kv.delete(key)
# 编码字典
def encode_dict(my_dict):
return "\x1e".join("%s\x1f%s" % x for x in my_dict.iteritems())
# 解码字典
def decode_dict(my_string):
return dict(x.split("\x1f") for x in my_string.split("\x1e"))
- 只存储了key和图片url,其他暂时是固定信息,便于以后扩展。
# 上传文件到七牛
@app.route('/upQiniu', methods=['POST', 'GET'])
def upQiniu():
if request.form.get('key'):
url = request.form.get('url')
upkey = request.form.get('key')
# 时间差,用于加载排序
import time
future_time = int(time.mktime(datetime.strptime('3000-01-01 00:00:00.000', "%Y-%m-%d %H:%M:%S.%f").timetuple()) * 1000)
uid = future_time - int(upkey)
# 存入DB
key = 'picbed_%s' % uid
object = 'Sunset Lake'
words = 'A peaceful sunset view...'
author = 'skyway'
# print key
data = {'upkey': upkey, 'object': object, 'words': words, 'author': author, 'url': url}
kv.set(str(key), data)
# kv.set_value(str(key), url)
return 'success'
浏览
访问获取key
# 初始加载
key_values = kv.get_by_prefix('picbed_', 20, None)
# print sorted(kv.getkeys_by_prefix('picbed', 3, None))
data = tuple(key_values)
json_data = []
for item in data:
tmp = {}
tmp['key'] = item[0]
if '\x1e' in item[1]:
content = decode_dict(item[1])
# print content
tmp['url'] = content['url'] + "?imageView2/2/w/400/format/jpg"
tmp['object'] = content['object']
tmp['words'] = content['words']
tmp['upkey'] = content['upkey']
tmp['author'] = content['author']
else:
tmp['url'] = item[1] + "?imageView2/2/w/400/format/jpg"
tmp['object'] = 'Sunset Lake'
tmp['words'] = 'A peaceful sunset view...'
tmp['upkey'] = item[1][-13:]
tmp['author'] = 'skyway'
json_data.append(tmp)
# data = sorted(data, reverse=True)
# print data
return render_template('PicBed.html', data=json_data, domain=config.PIC_DOMAIN)
瀑布流和数据加载
- Server端返回json数据
# 加载更多
if request.form.get('key'):
key = request.form.get('key')
# print key
key_values = kv.get_by_prefix('picbed_', 10, key)
# print sorted(kv.getkeys_by_prefix('picbed', 3, key))
data = tuple(key_values)
# data = sorted(data, reverse=True)
print data
json_data = []
for item in data:
tmp = {}
tmp['key'] = item[0]
if '\x1e' in item[1]:
content = decode_dict(item[1])
# print content
tmp['url'] = content['url'] + "?imageView2/2/w/400/format/jpg"
tmp['object'] = content['object']
tmp['words'] = content['words']
tmp['upkey'] = content['upkey']
tmp['author'] = content['author']
else:
tmp['url'] = item[1] + "?imageView2/2/w/400/format/jpg"
tmp['object'] = 'Sunset Lake'
tmp['words'] = 'A peaceful sunset view...'
tmp['upkey'] = item[1][-13:]
tmp['author'] = 'skyway'
json_data.append(tmp)
json_str = json.dumps(json_data)
return json_str
- 加载更多和渲染数据:
$(window).scroll(function () {
var clientHeight = $(window).height(),
scrollTop = $(window).scrollTop(),
scrollHeight = $(document).height();
if (!isScroll && (scrollHeight - clientHeight - scrollTop < 500)) {
isScroll = 1;
$('#loading').show();
// 获取最后一个块的key
var key = $('.grid:last-child').data('key');
// 请求数据
$.ajax({
url:"/picbed",
type: "POST",
data: { key: key}
})
.done(function(data){
if(data === 'error') {
alert('加载失败');
location.reload(true);
return false;
}
var pics = JSON.parse(data);
//没有更多了
if(jQuery.isEmptyObject(pics)){
$('#load_img').hide();
$('#load_text').text('没有更多了!');
$('#contain').BlocksIt('reload');
return false;
}
$.each(pics, function(index, val){
var add =
"<div class='grid' data-key='" + this.key +"'>" +
"<div class='imgholder'>" +
"<a class='fopLink' data-key='"+ this.upkey + "'>" +
"<img class='img_load' src='" + this.url +"' />" +
"</a>" +
"</div>" +
"<span class='stop'></span>" +
"<strong>"+ this.object +"</strong>" +
"<div class='description'>" +
"<p>"+ this.words +"</p>" +
"<div class='meta'>by " + this.author +"</div>" +
"</div>"+
"</div>";
$("#contain").append(add);
// 动态加载渲染图片
img_stop();
});
$('#contain').BlocksIt('reload');
});
}
});
- 瀑布流效果使用插件Blockits.js
效果不是很好,会产生重叠
长图渲染
对于过长的图片,影响整个浏览的体验,于是超过一定长度的图片,超出部分隐藏,并在图片尾部添加波浪表示图片未展示完全。
// 渲染图片函数
var img_stop = function(){
var height = 800;
$('.img_load').each(function() {
$(this).load(function(){
if( $(this).height() > height){
$(this).parent().parent().next().css('display', 'block');
}
});
});
};
// 初始加载渲染图片
$(function(){
img_stop();
});
var img_load = function(url, key) {
var img = new Image();
img.src = url;
img.onload = load_img(img, key);
function load_img(img, key){
var height = img.height;
if( height > 500){
var str = 'div[data-key=' + key + ']';
$(str).find('.stop').css('display', 'block');
}
}
};
效果
-
PC端
-
移动端