文件存储方案
- 在项目中,如用户头像、文章图片等数据往往需要使用单独的文件存储系统来保存
- 企业中常见的存储方案有两种:
a.搭建分布式存储系统, 如FastDFS
数据量非常大, 具备相应的运维管理人员
b.第三方存储
运维成本低, 安全可靠
七牛云
七牛云作为老牌云存储服务商, 提供了优质的第三方存储服务
官方网站 七牛云
使用步骤
- 注册用户, 需要实名认证
- 新建存储空间
- 默认生成测试域名, 有效期为一个月(要长期使用则需要绑定自己的域名)
以图形化的方式上传文件
直接在使用的地方引用外连接就可以访问了
交互过程
七牛云提供了多种语言的工具包以供开发者使用, 所以上传文件可以有以下两种形式:
- 由前端直接上传文件到七牛
- 由前端先将文件上传到后端web应用, 再由web应用上传七牛云
实际开发中应该 优先考虑客户端直接发送数据给七牛云, 交互过程如下:
此处为了学习七牛云工具的使用, 为以后python作为客户端进行第三方存储进行知识储备, 先将头像数据上传到web应用再发给七牛云进行存储, 交互过程如下:
安装
直接安装:
pip install qiniu
或
easy_install qiniu
上传代码:
# -*- coding: utf-8 -*-
# flake8: noqa
from qiniu import Auth, put_file, etag
import qiniu.config
#需要填写你的 Access Key 和 Secret Key
access_key = 'Access_Key'
secret_key = 'Secret_Key'
#构建鉴权对象
q = Auth(access_key, secret_key)
#要上传的空间
bucket_name = 'Bucket_Name'
#上传后保存的文件名
key = 'my-python-logo.png'
#生成上传 Token,可以指定过期时间等
token = q.upload_token(bucket_name, key, 3600)
#要上传文件的本地路径
localfile = './sync/bbb.jpg'
ret, info = put_file(token, key, localfile, version='v2')
print(info)
assert ret['key'] == key
assert ret['hash'] == etag(localfile)
封装成函数,进行相应的配置
from qiniu import Auth, put_file, etag
def upload_file(data):
'''
上传文件到七牛云
:param data 要上传的二进制数据
:return: 头像url
'''
#需要填写你的 Access Key 和 Secret Key
access_key = 'gSMqHNP7ugq3S53n48vdcKQkGdsylzT5o4Bkrr5a'
secret_key = '****************************************'
#构建鉴权对象
q = Auth(access_key, secret_key)
#要上传的空间
bucket_name = 'jibu123'
#上传后保存的文件名(如果设置为None,七牛云会自动生成文件名,可以保证文件名的唯一性,本质就是使用文件的哈希值作为文件名)
# 哈希函数:将任意内容转为定长的字符串,而且具有唯一性,一般称哈希值也叫做文件的指纹
#比如第一次上传a文件第二次再上传a图片就会特别快,就是因为两次哈希值一样,所以就不用再上传了
key = None
#生成上传 Token,可以指定过期时间等
token = q.upload_token(bucket_name, key, 3600)
ret, info = put_file(token, key, data)
if info.statis_code==200: # 上传成功
# 域名是固定的 可以获取到上传后的文件名
return 'http://r8fj0mnul.hn-bkt.clouddn.com/' + ret.get('key')
else:
raise BaseException(info.error)
if __name__ == '__main__':
with open('kobe.png','rb') as f:
file_bytes = f.read()
file_url = upload_file(file_bytes)
项目集成
- 在 app/settings/config.py文件中设置 七牛云 的相关配置
class DefaultConfig:
"""默认配置"""
...
# 七牛云
QINIU_ACCESS_KEY = 'kJ8wVO7lmFGsdvtI5M7eQDEJ1eT3Vrygb4SmR00E'
QINIU_SECRET_KEY = 'rGwHyAvnlLK7rU4htRpNYzpuz0OHJKzX2O1LWTNl'
QINIU_BUCKET_NAME = 'sh32'
QINIU_DOMAIN = 'http://q7f6ieukf.bkt.clouddn.com/'
- 在 /common/utils包中, 创建 img_storage.py文件, 在其中定义文件上传函数
from qiniu import Auth, put_data, etag
from flask import current_app
def upload_file(data):
"""
上传文件到七牛云
:param data: 要上传的二进制数据
:return: 文件的名称
"""
# 需要填写你的 Access Key 和 Secret Key
access_key = current_app.config['QINIU_ACCESS_KEY']
secret_key = current_app.config['QINIU_SECRET_KEY']
# 构建鉴权对象
q = Auth(access_key, secret_key)
# 要上传的空间
bucket_name = current_app.config['QINIU_BUCKET_NAME']
# 上传后保存的文件名
key = None # 如果设置key=None, 则七牛云会自动生成名称(该名称会通过文件的哈希值生成, 不同文件的哈希值不同, 文件名不会出现冲突)
# 哈希函数: 可以将任意长度的内容转为定长内容, 而且相同内容的哈希值一样, 不同内容的哈希值不同
# 生成上传 Token,可以指定过期时间等
token = q.upload_token(bucket_name, key, 3600)
ret, info = put_data(token, key, data)
if info.status_code == 200: # 上传成功
return current_app.config['QINIU_DOMAIN'] + ret.get('key') # 获取并返回文件URL
else:
raise Exception(info.error)
if __name__ == '__main__':
with open('123.jpg', 'rb') as f:
data = f.read() # 模拟前端上传的二进制数据
file_url = upload_file(data)
print(file_url)
PUT(全部更新)
PATCH(部分更新)
接口设计
# 上传头像
/user/photo
# 请求方式
PATCH form-data
# 请求参数
photo 上传的头像文件
# 响应数据 json
{
"photo_url": "www.xx.com/123.jpg"
}
上传文件视图
# /app/resources/user/profile.py
...
from flask_restful.reqparse import RequestParser
from utils.parser import image_file
from utils.img_storage import upload_file
from app import db
from flask import current_app
...
class UserPhotoResource(Resource):
method_decorators = [login_required]
def patch(self):
"""修改头像"""
# 获取参数
userid = g.userid
parser = RequestParser()
# 如果没有指定type会账指转换为字符串类型
parser.add_argument('photo', required=True, type=FileStorage, location='files')
args = parser.parse_args()
img_file = args.photo
# f = request.files.get('photo')
# f.save()
# f.read()
# 读取二进制数据
img_bytes = img_file.read()
# 上传到七牛云
try:
file_url = upload_file(img_bytes)
except BaseException as e:
return {'message': 'Thired Error: %s' % e, 'data': None}, 500
# 将数据库中头像URL进行更新
User.query.filter(User.id == userid).update({'profile_photo': file_url})
db.session.commit()
# 返回URL
return {'photo_url': file_url}
3. 检查图片文件
python的内置模块imghdr中封装了检查文件是否为图片类型的函数what(), 可以利用该函数检查前端上传的数据是否为图片类型
import imghdr
# with open('222.png', 'rb') as f:
# # 判断文件类型 一般是通过文件的头部字节进行匹配
# content = f.read()
# print(content)
if __name__ == '__main__':
with open('666.jpg', 'rb') as f:
# 判断文件是否为图片类型
# 方式1: 传递 文件路径/文件对象
# type = imghdr.what('666.jpg')
# type = imghdr.what(f)
# print(type)
# 方式2: 传递 二进制数据
data_bytes = f.read()
type = imghdr.what(None, data_bytes)
print(type)
检查图片封装
def image_file(value):
"""
检查是否是图片文件
:param value:
:return:
"""
try:
file_type = imghdr.what(value)
except Exception:
raise ValueError('Invalid image.')
else:
if file_type:
return value
else:
raise ValueError('Invalid image.')