然后, 得弄个脚本把数据库里面已经有的图片给加上时间戳.
顺带吐个槽, 其实 NoSQL DB 在这种环境下根本体现不出任何优势, 用起来跟 RDB 几乎没两样.
2.7 利用 SHA-1 排重
与冰箱里的可乐不同, 大部分情况下你肯定不希望数据库里面出现一大波完全一样的图片. 图片, 连同其 EXIFF 之类的数据信息, 在数据库中应该是惟一的, 这时使用略强一点的散列技术来检测是再合适不过了.
达到这个目的最简单的就是建立一个 SHA-1 惟一索引, 这样数据库就会阻止相同的东西被放进去.
在 MongoDB 中表中建立惟一 索引 , 执行 (Mongo 控制台中)
db.files.ensureIndex({sha1: 1}, {unique: true})
如果你的库中有多条记录的话, MongoDB 会给报个错. 这看起来很和谐无害的索引操作被告知数据库中有重复的取值 null (实际上目前数据库里已有的条目根本没有这个属性). 与一般的 RDB 不同的是, MongoDB 规定 null, 或不存在的属性值也是一种相同的属性值, 所以这些幽灵属性会导致惟一索引无法建立.
解决方案有三个:
1)删掉现在所有的数据 (一定是测试数据库才用这种不负责任的方式吧!)
2)建立一个 sparse 索引, 这个索引不要求幽灵属性惟一, 不过出现多个 null 值还是会判定重复 (不管现有数据的话可以这么搞)
3)写个脚本跑一次数据库, 把所有已经存入的数据翻出来, 重新计算 SHA-1, 再存进去
具体做法随意. 假定现在这个问题已经搞定了, 索引也弄好了, 那么剩是 Python 代码的事情了.
import hashlib
def save_file(f):
content = StringIO(f.read())
try:
mime = Image.open(content).format.lower()
if mime not in allow_formats:
raise IOError()
except IOError:
flask.abort(400)
sha1 = hashlib.sha1(content.getvalue()).hexdigest()
c = dict(
content=bson.binary.Binary(content.getvalue()),
mime=mime,
time=datetime.datetime.utcnow(),
sha1=sha1,
)
try:
db.files.save(c)
except pymongo.errors.DuplicateKeyError:
pass
return c['_id']
在上传文件这一环就没问题了. 不过, 按照上面这个逻辑, 如果上传了一个已经存在的文件, 返回 c['_id'] 将会是一个不存在的数据 ID. 修正这个问题, 最好是返回 sha1 , 另外, 在访问文件时, 相应地修改为用文件 SHA-1 访问, 而不是用 ID.
最后修改的结果及本篇完整源代码如下 :
import hashlib
import datetime
import flask
import pymongo
import bson.binary
import bson.objectid
import bson.errors
from cStringIO import StringIO
from PIL import Image
app = flask.Flask(__name__)
app.debug = True
db = pymongo.MongoClient('localhost', 27017).test
allow_formats = set(['jpeg', 'png', 'gif'])
def save_file(f):
content = StringIO(f.read())
try:
mime = Image.open(content).format.lower()
if mime not in allow_formats:
raise IOError()
except IOError:
flask.abort(400)
sha1 = hashlib.sha1(content.getvalue()).hexdigest()
c = dict(
content=bson.binary.Binary(content.getvalue()),
mime=mime,
time=datetime.datetime.utcnow(),
sha1=sha1,
)
try:
db.files.save(c)
except pymongo.errors.DuplicateKeyError:
pass
return sha1
@app.route('/f/<sha1>')
def serve_file(sha1):
try:
f = db.files.find_one({'sha1': sha1})
if f is None:
raise bson.errors.InvalidId()
if flask.request.headers.get('If-Modified-Since') == f['time'].ctime():
return flask.Response(status=304)
resp = flask.Response(f['content'], mimetype='image/' + f['mime'])
resp.headers['Last-Modified'] = f['time'].ctime()
return resp
except bson.errors.InvalidId:
flask.abort(404)
@app.route('/upload', methods=['POST'])
def upload():
f = flask.request.files['uploaded_file']
sha1 = save_file(f)
return flask.redirect('/f/' + str(sha1))
@app.route('/')
def index():
return '''
<!doctype html>
<html>
<body>
<form action='/upload' method='post' enctype='multipart/form-data'>
<input type='file' name='uploaded_file'>
<input type='submit' value='Upload'>
</form>
'''
if __name__ == '__main__':
app.run(port=7777)










