1、功能说明:
脚本基于Flask实现简单文件上传下载,登录接口二次鉴权且,内部接口基于Token认证
2、目录结构:
flask-upload/ #项目目录
conf/ #配置文件目录
appconf.py #配置文件
__init__.py
lib/ #库文件存放路径
templates/ #模板文件存放路径
upload.html #前端页面upload上传页面
upload/ #上传文件保存路径
main.py #主程序
README.md #说明文档
start.sh #启动文件
3、代码展示:
# _*_ coding:utf-8 _*_
''' 用于文件上传下载 '''
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import os
basedir = os.path.dirname(os.path.abspath(__file__))
libdirs = os.path.join(basedir,'lib')
confdir = os.path.join(basedir,'conf')
logpath = os.path.join(basedir,'logs')
inifile = os.path.join(confdir,'__init__.py')
appfile = os.path.join(confdir,'appconf.py')
sys.path.insert(0,libdirs)
import time
import hmac
import base64
import random
import string
import hashlib
from werkzeug.utils import secure_filename
from flask import Flask,render_template,jsonify,request,send_from_directory,redirect,url_for,make_response
app=Flask(__name__)
app.debug=True
#生成随机8位字符串
SALT = ''.join(random.sample(string.ascii_letters + string.digits, 8))
#生成默认Conf配置文件
if os.path.isdir(confdir) is False:
os.makedirs(confdir)
with open(inifile,'w') as f:
f.write('from appconf import init\nConf = init()\n')
if os.path.exists(appfile) is False:
with open(appfile,'w') as f:
f.write('''# _*_ coding:utf-8 _*_
def init():
return {
'global':{
'default_key':'000000',
'upload_dir':'upload', #默认上传路径
'secret_key':'key%s', #Token加盐salt值
'expire_time':3600, #Token过期时间
'limit_size': 100 * 1024 * 1024,
'allowed_extensions':['sh','py','gz','tar.gz','zip','tar'] #支持上传的文件类型
},
'ulogin':{
'admin': '%s' #默认登录账户密码
}
}
''' % (int(time.time()),SALT))
from conf import Conf
URINFO=Conf['ulogin']
SECKEY=Conf['global']['secret_key']
EXPIRE=Conf['global']['expire_time']
UPLOAD_FOLDER=Conf['global']['upload_dir']
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH']=Conf['global']['limit_size']
ALLOWED_EXTENSIONS = set(Conf['global']['allowed_extensions'])
# 用于判断文件后缀
def allowed_file(filename):
return '.' in filename and filename.rsplit('.',1)[1] in ALLOWED_EXTENSIONS
#登录接口(需要二次登录通过Seckey登录)
@app.route('/login',methods=['GET','POST'])
def do_login():
username = password = None
try:
if request.method == 'GET':
username=request.args.get('username',None)
password=request.args.get('password',None)
if request.method == 'POST':
username=request.json.get('username',None)
password=request.json.get('password',None)
if username is None:
return jsonify({"code":1,"message":"The username is empty"})
if password is None:
return jsonify({"code":1,"message":"The password is empty"})
if username not in URINFO:
return jsonify({"code":1,"message":"The username is not exists"})
if URINFO.get(username) not in password:
return jsonify({"code": 1,"message": "The password is error"})
tmp_code=Conf['global']['default_key']
if request.cookies.has_key('Seckey'):
tmp_code=request.cookies.get('Seckey',tmp_code)
if password != URINFO.get(username)+ tmp_code:
tmp_code = ''.join(random.sample(string.ascii_letters + string.digits,16))
response = make_response(jsonify({"code": 0,"seckey":tmp_code}))
response.set_cookie('Seckey', tmp_code)
response.set_cookie('Username', username)
return response
Token=encrypt_token(key=username,expire=EXPIRE)
response=make_response(jsonify({"code":0,"message":"Login successful"}))
response.set_cookie('Username',username)
response.set_cookie('Token',Token)
return response
except Exception,e:
response = make_response(jsonify({"code":1,"message":"Login failed"}))
response.set_cookie('Username',username)
return response
#注销接口
@app.route('/logout',methods=['GET'])
def do_logout():
Token=request.cookies.get('Token',None)
Username=request.cookies.get('Username',None)
if decrypt_token(token=Token,key=Username) is False:
return jsonify({"code":1,"message":"User logout fails,Token authentication fails"})
try:
response=make_response(jsonify({"code":0,"message":"User logout successful"}))
response.delete_cookie('Username',Username)
response.delete_cookie('Token',Token)
return response
except Exception:
return jsonify({"code": 1, "message": "User logout failed"})
#生成Token
def encrypt_token(key=SECKEY,expire=EXPIRE):
time_str=str(time.time() + expire).encode('utf-8')
hmac_str=hmac.new(key.encode('utf-8'),time_str,hashlib.md5().update(SECKEY)).hexdigest()
hash_str=hmac_str.encode('utf-8') + ':' + time_str
token_str=base64.urlsafe_b64encode(hash_str).encode('utf-8')
return token_str
#验证Token
def decrypt_token(token,key=SECKEY):
try:
token_str=base64.urlsafe_b64decode(token.encode('utf-8'))
hash_str=token_str.split(':')
if len(hash_str) != 2:
return False
hmac_str=hash_str[0]
time_str=hash_str[1]
if time.time() > float(time_str):
return False
old_hmac_str=hmac.new(key.encode('utf-8'),time_str.encode('utf-8'),hashlib.md5().update(SECKEY)).hexdigest()
if hmac_str != old_hmac_str:
return False
return True
except Exception:
return False
#上传页面
@app.route('/upload')
def upload_render():
Token=request.cookies.get('Token',None)
Username=request.cookies.get('Username',None)
if decrypt_token(token=Token,key=Username) is False:
return jsonify({"code":1,"message":"Token Authentication failed"})
return render_template('upload.html')
#下载接口
@app.route('/download',methods=['GET'],strict_slashes=False)
def download():
Token=request.cookies.get('Token',None)
Username = request.cookies.get('Username', None)
if decrypt_token(token=Token,key=Username) is False:
return jsonify({"code":1,"message":"Token Authentication failed"})
filename=request.args.get('file',None)
if filename is None:
return jsonify({"code":1,"message":"File is None"})
if os.path.isfile(os.path.join('upload', filename)):
return send_from_directory('upload',filename,as_attachment=True)
return jsonify({"code": 1, "message": "File does not exist"})
#上传接口
@app.route('/api/upload',methods=['POST','GET'],strict_slashes=False)
def api_upload():
Token=request.cookies.get('Token',None)
Username = request.cookies.get('Username', None)
if decrypt_token(token=Token,key=Username) is False:
return jsonify({"code":1,"message":"Token Authentication failed"})
if request.method == 'GET':
return render_template('upload.html')
file_dir=os.path.join(basedir,app.config['UPLOAD_FOLDER'])
if not os.path.exists(file_dir):
os.makedirs(file_dir)
f=request.files['myfile'] # 从表单的file字段获取文件,myfile为该表单的name值
try:
if f.filename == '':
return render_template('upload.html')
if f and allowed_file(f.filename): # 判断是否是允许上传的文件类型
fname=secure_filename(f.filename)
fname=str(fname).encode('utf-8')
if os.path.exists(os.path.join(file_dir,fname)) is False:
f.save(os.path.join(file_dir,fname))
else:
eot = fname.split('.',1)[0].encode('utf-8')
ext = fname.split('.',1)[1] # 获取文件后缀
unix_time = int(time.time())
new_filename=eot+'_'+str(unix_time)+'.'+ext # 修改了上传的文件名
f.save(os.path.join(file_dir,new_filename)) #保存文件到upload目录
time.sleep(3)
return redirect(url_for('upload_render'))
else:
return jsonify({"code":1,"message":"File type not allowed for upload"})
except Exception:
return jsonify({"code": 1, "message": "File type not allowed for upload"})
if __name__ == '__main__':
app.run(host='0.0.0.0',port=5678)
接口验证:
登录接口:http://ip:5678/login?username=用户&password=密码,用户密码请看conf/appconf.py配置文件ulogin配置块
验证上传接口:
http://ip地址:5678/upload
由于未二次登录所有上传接口Token验证失败,执行二次登录如下:
注意二次登录password=密码+seckey
登录成功如下:
再次刷新上传接口已经正常显示界面
执行文件上传
验证文件上传
文件下载:
http://ip地址:5678/download?file=文件名称
注销登录:
http://ip地址:5678/logout
至此文件上传下载验证完成,下载地址如下:
链接:https://pan.baidu.com/s/1q1CA-IRxqQXwUEXKD67Yzg
提取码:1avq