flask登录的大型应用初探
0.所谓“大型应用”是指falsk官网(https://dormousehole.readthedocs.io/en/latest/patterns/packages.html) 大型应用 篇的说法,并不是我要构建一个基于 登录的大型工程项目。
1.前面几次,从前端到后端再到数据库都做了简单尝试,目前已经能做到前后端互联互通了。一个上道儿的程序员应该有一定程度的代码洁癖和归纳癖。本次就尝试依照flask提供的推荐方法,归拢一下往日的后端代码。
2.下面几篇文章必须仔细阅读。归拢方法都源于此:
2.1 应用上下文:https://dormousehole.readthedocs.io/en/latest/appcontext.html
2.2 大型应用:https://dormousehole.readthedocs.io/en/latest/patterns/packages.html
2.3 使用 Setuptools 部署: https://dormousehole.readthedocs.io/en/latest/patterns/distribute.html
2.4 官方示例代码:https://github.com/pallets/flask/tree/1.1.1/examples/tutorial
3. 核心代码没有做过调整。仅仅是创建了setup.py文件 然后将原有代码移入hellologin目录下,改造后的目录结构如下:
4.下面把所有代码重新粘贴出来:
from setuptools import setup setup( name='hellologin', version='1.0', long_description=__doc__, packages=['hellologin'], include_package_data=True, zip_safe=False, install_requires=['Flask'] )
import os from flask import Flask, jsonify from flask_cors import CORS def create_app(test_config=None): app = Flask(__name__) app.secret_key =b'\x15f\x07\xd3\xd9\xbf*\x82\xd1\xe6\xb4\xf2\x95\xdd\x8f\x12' #命令行中运行后拷贝出随机值 python -c "import os; print(os.urandom(16))" CORS(app,supports_credentials=True) @app.route('/hello') def helloworld(): returnData = {'code': 0, 'msg': 'success', 'data': 'hello world' } return jsonify(returnData),200 app.add_url_rule("/", endpoint="helloworld") with app.app_context(): from hellologin import login app.register_blueprint(login.login_page) return app
from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.ext.declarative import declarative_base engine = create_engine("mysql+mysqlconnector://root:123@localhost:3306/hello_login", encoding="utf-8",echo=True) db_session = scoped_session(sessionmaker(autocommit=False,autoflush=False,bind=engine)) Base = declarative_base() Base.query = db_session.query_property() def init_db(): # 在这里导入定义模型所需要的所有模块,这样它们就会正确的注册在 # 元数据上。否则你就必须在调用 init_db() 之前导入它们。 import models Base.metadata.create_all(bind=engine)
import jwt from jwt import PyJWTError from datetime import datetime,timedelta SECRECT_KEY = b'\x92R!\x8e\xc6\x9c\xb3\x89#\xa6\x0c\xcb\xf6\xcb\xd7\xbc' def genToken(exp,data): payload = { 'exp': exp, 'data': data } token = jwt.encode(payload,key= SECRECT_KEY,algorithm= 'HS256') return bytes.decode(token) def verfiyToken(tokenStr): try: tokenBytes = tokenStr.encode('utf-8') payload = jwt.decode(tokenBytes,key= SECRECT_KEY,algorithm= 'HS256') return payload except PyJWTError as e: print("jwt验证失败: %s" % e) return None
import time import json from flask import Blueprint,request,current_app,jsonify from flask_login import LoginManager,login_user,logout_user,login_required,current_user from hellologin.user import UserLogin login_page = Blueprint('login_page',__name__) login_manager = LoginManager() login_manager.login_view = None login_manager.init_app(current_app) @login_manager.request_loader def load_user_from_request(request): token = request.headers.get('Authorization') if token == None: return None payload = UserLogin.verfiyUserToken(token) if payload != None: alternativeID = payload['data']['alternativeID'] sessionID = payload['data']['sessionID'] user = UserLogin.queryUser(alternativeID=alternativeID,sessionID=sessionID) else: user = None return user @login_page.route('/hello2') def hello(): returnData = {'code': 0, 'msg': 'success', 'data': 'hello login' } return jsonify(returnData),200 @login_page.route('/first') @login_required def firstPage(): returnData = {'code': 0, 'msg': 'success', 'data': {'token':current_user.token,'tips':'First Blood(来自' + current_user.userName +')'}} return returnData,200 @login_page.route('/login', methods=['POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] user = UserLogin.queryUser(userName = username) if (user != None) and (user.verifyPassword(password)) : login_user(user) returnData = {'code': 0, 'msg': 'success', 'data': {'token':user.token}} return json.dumps(returnData),200 else : returnData = {'code': 1, 'msg': 'failed', 'data': {'tips':'username or password is not correct'} } return json.dumps(returnData),200 @login_page.route('/logout') @login_required def logout(): userName = current_user.userName # alternativeID = current_user.alternativeID sessionID = current_user.sessionID UserLogin.dropSessionID(sessionID) logout_user() returnData = {'code': 0, 'msg': 'success', 'data': {'tips':'Bye ' + userName} } return json.dumps(returnData),200 @login_page.route('/changepws') @login_required def changePws(): user = UserLogin.queryUser(userID = current_user.id) user.changePws() returnData = {'code': 0, 'msg': 'success', 'data': {'tips':'password was changed'} } return json.dumps(returnData),200
from sqlalchemy import ForeignKey,Column, Integer, String, Date,BigInteger,DateTime from sqlalchemy.orm import relationship,backref from werkzeug.security import check_password_hash,generate_password_hash from hellologin.database import Base import uuid class Operator(Base): __tablename__ = 'operators' id = Column(Integer, primary_key=True) username = Column(String(50), unique=True,nullable=False) password = Column(String(200), unique=False,nullable=False) alternativeID = Column(String(200), unique=True,nullable=False) def __init__(self, id=None,username=None, password=None): self.id = id self.username = username self.password = generate_password_hash(username + password) #加盐加密函数,通过随机产生不同salt(盐粒)混入原文,使每次产生的密文不一样。 self.alternativeID = str(uuid.uuid1()) def __repr__(self): return '<operator %r>' % (self.username) def verifyPassword(self,password=None): if password is None: return False return check_password_hash(self.password,self.username + password) class OperatorSession(Base): __tablename__ = 'oper_sessions' id = Column(Integer, primary_key=True) sessionID = Column(String(200), nullable=False) exp_utc = Column(DateTime, nullable=False) operator_id = Column(Integer, ForeignKey('operators.id'),nullable=False) operator = relationship(Operator,backref=backref('operators', uselist=True, cascade='delete,all')) # class OperatorSession(Base): # __tablename__ = 'oper_sessions' # # __table_args__ = {'prefixes': ['TEMPORARY']} # id = Column(Integer, primary_key=True) # sessionID = Column(String(200), nullable=False) # exp_utc = Column(DateTime, nullable=False) # operator_id = Column(Integer, ForeignKey('operators.id'),nullable=False) # def __repr__(self): # return 'seesionID: %r' % self.seesionID
import uuid from flask_login import UserMixin from werkzeug.security import check_password_hash,generate_password_hash from datetime import datetime,timedelta,time from hellologin.jwt_token import genToken,verfiyToken from hellologin.database import init_db,db_session from hellologin.models import Operator,OperatorSession class UserLogin(UserMixin):#之所以命名为UserLogin,只是为了区分数据库的User表。此类仅仅是为了登陆管理而存在 def __init__(self,operater,sessionID=None): self.id = operater.id self.userName = operater.username self.alternativeID = operater.alternativeID self.oper = operater self.sessionID = None self.token = None exp = datetime.utcnow() + timedelta(seconds=60) self.genSessionID(exp,sessionID) self.genToken(exp) def get_id(self): return self.id def get(user_id): if not user_id: return None user = Operator.query.filter_by(id= userID).first() return UserLogin(user) def verifyPassword(self,password=None): return self.oper.verifyPassword(password) def genSessionID(self,exp,sessionID=None): if sessionID == None: self.sessionID = str(uuid.uuid4()) os = OperatorSession(sessionID= self.sessionID,exp_utc= exp,operator= self.oper) db_session.add(os) # self.oper.sessions.append(OperatorSession(sessionID= self.sessionID,exp_utc= exp)) db_session.commit() else: self.sessionID = sessionID def genToken(self,exp): token = genToken(exp,{'alternativeID':self.alternativeID,'sessionID':self.sessionID}) self.token = token return token @staticmethod def queryUser(**kwargs): if 'userName' in kwargs: username = kwargs['userName'] user = Operator.query.filter_by(username = username).first() return UserLogin(user) elif ('alternativeID' in kwargs) and ('sessionID' in kwargs): alternativeID = kwargs['alternativeID'] sessionID = kwargs['sessionID'] user = db_session.query(Operator).filter_by(alternativeID=alternativeID).join(OperatorSession).filter_by(sessionID=sessionID).first() if user: return UserLogin(user,sessionID) else: return None @staticmethod def verfiyUserToken(token): payload = verfiyToken(token) if not payload : removeSessions = db_session.query(OperatorSession).filter(OperatorSession.exp_utc < datetime.utcnow()).delete(synchronize_session=False) db_session.commit() return payload @staticmethod def dropSessionID(sessionID): removeSession = db_session.query(OperatorSession).filter_by(sessionID=sessionID).first() db_session.delete(removeSession) db_session.commit()
5.运行以上代码,当然依然需要在venv中(之所以改成flask run 运行,是由于此文章:https://dormousehole.readthedocs.io/en/latest/server.html 此文说明了两者差别,不过目前本人还没体会到优势。)
5.1 venv\scripts\activate
5.2 $env:FLASK_APP="hellologin"
5.3 flask run
6.其实已经能顺利和前端代码关联。而且前端不需要任何调整。
7.接下来,需要认真体会模块后的好处。到目前为止,我没有感受到任何优势。还需要再理解几遍官方文档。