目录
一:flask框架整体构造
1.介绍
2.构造图
二,核心对象、管理、启动模块
1.核心对象app.py模块
2.管理模块manager.py
3.启动模块server.py
三:配置文件模块
四:forms验证模块
五:libs公共库模块
1.auth权限认证模块
2.error_code认证状态异常模块
3.handler重写处理异常函数
4.response定义标准化返回
六:model数据库模块
1.__init__.py
2.userdb.py数据库模块
七:route路由模块
1.__init__.py
2.student.py
八:回顾总体web项目流程
flask框架实现web平台开发,其中包含config配置,forms验证相关,libs公共库,mifrations同步数据库表单,model数据库,route路由函数,app.py实例化核心对象,manager.py迁移脚本和管理工具,server.py启动程序。
主要做的事情:生成实例化对象,同步配置文件,注册蓝图模块,注册数据类型,绑定序列化模型。
import os
from flask import Flask
def create_app(config=None):
app = Flask(__name__)
# 先创建一个核心对象 然后加载配置文件
# load default configuration
# 使用模块导入的方式配置文件
app.config.from_object("config.settings")
# 根据系统环境的不同去加载不同的配置文件
if "FLASK_CONF" in os.environ:
app.config.from_envvar("FLASK_CONF")
# 传递参数来加载配置文件
if config is not None:
if isinstance(config,dict):
app.config.update(config)
elif config.endswith(".py"): #如果是文件,加载文件的
app.config.from_pyfile(config)
# 注册蓝图
import route
route.init_app(app)
# 注册数据类型
import model
model.init_db_app(app)
# 绑定序列化模型
import serializer
serializer.init_app(app)
return app
主要工作:使用migrat模块管理数据库,同步数据,更新字段。
# 使用命令行去启动服务flask_script模块
# python manager.py runserver -h 0.0.0.0 -p 9000 -d
from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand
from model import db
from app import create_app
hyrzapp = create_app()
manager = Manager(hyrzapp)
# 创建db管理工具
migrate = Migrate(hyrzapp,db)
# 添加迁移脚本的命令到manager中--->python manager.py db init
manager.add_command("db",MigrateCommand)
if __name__ == "__main__":
manager.run()
主要工作:把上下文管理器对象从栈对拉去下来,并指定配置文件内容
from app import create_app
hyrzapp = create_app()
# #使用current_app 需要先将上下文管理器对象推送到栈里去
ctx = hyrzapp.app_context()
ctx.push()
# from route import user
# from route import student
# import route
# route.init_app(hyrzapp)
hyrzapp.run(host=hyrzapp.config["HOST"],
port=hyrzapp.config["PORT"],
debug=hyrzapp.config["DEBUG"])
可根据需求配置,通常指定ip,端口,连接数据库,debug状态等信息。
JSON_AS_ASCII = False
HOST = "0.0.0.0"
PORT = 9000
DEBUG = True
# 数据库+连接://用户名:密码@数据库主机/数据库
SQLALCHEMY_DATABASE_URI ='mysql+pymysql://sc:[email protected]/sc?charset=utf8'
SECRET_KEY="123456"
EXPIRES_IN=3600
验证分为注册验证和登录验证,注册模块根据客户端传过来的字段是否符合要求进行验证,登录模块需要通过用户数据表对比验证。
# 验证相关,验证与客户端传递过来的有关数据
# validators规则,DaraRequired必要参数,Email邮箱合法性,Regexp正则
from wtforms import Form, StringField
from wtforms.validators import DataRequired,Email,Regexp,ValidationError
from model.userdb import UserManager
from werkzeug.security import check_password_hash
# 验证客户端传过来的注册数据是否合格,并且必须要有四个属性
class UserForm(Form):
name = StringField(validators=[DataRequired()])
email = StringField(validators=[DataRequired(),Email(message="邮箱不合法")])
password = StringField(validators=[DataRequired(),Regexp(r'\w{6,18}',message="密码不符合要求")])
mobile = StringField(validators=[DataRequired(),Regexp(r'1\d{10}',message="电话不符合要求")])
#自定义验证器,验证邮箱是否唯一
#自定义检查字段,方法名:validate 你要检查的字段
def validate_email(self,value):
if UserManager.query.filter_by(useremail = value.data).first():
raise ValidationError("邮箱已存在")
def validate_name(self,value):
# 对数据进行修改,给客户端闯进来的所有用户都加一个开头
value.data = "sanchuang-"+value.data
# 验证登录
class LoginForm(Form):
userName = StringField(validators=[DataRequired(),Email(message="邮箱不合法")])
password = StringField(validators=[DataRequired(),Regexp(r'\w{6,18}',message="密码不合法")])
def validate(self):
super().validate()
if self.errors:
return False
user = UserManager.query.filter_by(useremail = self.userName.data).first()
if user and check_password_hash(user.password,self.password.data):
return user
else:
raise ValidationError("验证失败!")
使用itsdangerous模块中的TimedJSONWebSignatureSerializer,BadSignature,SignatureExpired等
# http是一个无状态的,每次请求都是独立的,互不影响
# session cookie --做会话保持,保持登录过的状态
# session是保存在服务器端的
# cookie是保存在客户端的
# 他俩的关系https://zhuanlan.zhihu.com/p/63061864
from flask import request,current_app
from libs.response import generate_response
from hashlib import md5
from model.userdb import ApiToken
import time
from flask_httpauth import HTTPBasicAuth
from itsdangerous import TimedJSONWebSignatureSerializer as TJS
from itsdangerous import BadSignature,SignatureExpired
from libs.error_code import TokenFailException
# http基本认证的对象
auth = HTTPBasicAuth()
# 装饰器1
def login_require(func):
def inner(*args,**kwargs):
# if request.args.get("username"):
if api_authorize():
return func(*args,**kwargs)
else:
return generate_response(status_code=10001,message="not allow")
return inner
# 这个函数返回为真 就是表示认证成功
# 返会为假 ,就是认证失败
# 被装饰的函数需要接收两个参数,解析authorization头部字段
@auth.verify_password
def verify_passed(token,password):
print(f"token is {token},password is {password}")
if token and password:
# 如果有token和password 就认证成功
return False
# 客户端请求的时候,携带头信息,authorization: basic case64(api:)
elif token and token == "api":
return api_authorize()
elif token:
# 只传了用户名字段,验证用户名字段的token
user = verify_token(token)
return True
# 验证:session cookie token
# 验证token是否合法
def verify_token(token):
# 生成一个对象s ,用相同的秘钥验证是否一致
s = TJS(current_app.config["SECRET_KEY"])
try:
data = s.loads(token)
except BadSignature:
raise TokenFailException
except SignatureExpired:
raise RuntimeError("token过期")
return data
# 用于API接口授权
def api_authorize():
params = request.args
appid = params.get("appid")
salt = params.get("salt") #盐值
sign = params.get("sign") #签名
timestamp = params.get("timestamp") #时间戳
if time.time() - int(timestamp) > 600:
return False
api_token = ApiToken.query.filter_by(appid = appid).first()
if not api_token:
return False
#验证有没有此url和方法的权限
if not has_permission(api_token, request.path, request.method.lower()):
return False
#获取数据库里的密钥
secretkey = api_token.secretkey
#生成服务端的签名
#可以加上时间戳来防止签名被别人盗取,重复访问
user_sign = appid + salt + secretkey + timestamp
m1 = md5()
m1.update(user_sign.encode(encoding="utf-8"))
#判断客户端传递过来的签名和服务端生成签名是否一致
if sign != m1.hexdigest():
# raise AuthFailException
return False
else:
return True
#url验证
def has_permission(api_token, url, method):
#客户端请求的方法和url
mypermission = method+url
#获取此api_token对象的所有url权限
all_permission = [permission.method_type+permission.url
for permission in api_token.manage]
if mypermission in all_permission:
return True
else:
return False
# 认证---》生成token
def create_token(uid):
# 第一个参数传递内部私钥,测试环境写死
# 第二个传递有效期
s = TJS(current_app.config["SECRET_KEY"],expires_in=current_app.config["EXPIRES_IN"])
# s = TJS("123456",expires_in=10)
# token遵循JWS规范
token = s.dumps({"uid":uid}).decode("ascii")
print(token)
return token
if __name__ == "__main__":
create_token(1)
使用的是werkzeug模块的HTTPException
# 异常处理
from werkzeug.exceptions import HTTPException
# 自定义异常类,继承本身的异常类 重写get_body,get_handler方法。
class APIException(HTTPException):
code = 500
message = "opps!"
status_code = 9999
def __init__(self,message=None,code=None,status_code=None):
if message:
self.message = message
if code:
self.code = code
if status_code:
self.status_code = status_code
super(APIException, self).__init__()
def get_body(self, environ = None, scope = None,) -> str:
body = dict(
message = self.message,
status_code = self.status_code
)
import json
content = json.dumps(body)
return content
def get_headers(self, environ=None,scope=None,):
return [('conntent-Type','applicatinon/json')]
# API授权接口异常类
class APIAuthorizedException(APIException):
message = "API授权认证失败"
status_code = 10002
code = 401
#表单异常
class FormValidateException(APIException):
message = "表单验证失败"
status_code = 10003
#token验证失败
class TokenFailException(APIException):
message = "token不合法,验证失败"
status_code = 10005
主要目的是使异常处理按照统一的标准进行
# 处理异常的函数
from flask_restful import HTTPException
from libs.error_code import APIException
def default_error_handler(ex):
if isinstance(ex,APIException):
return ex
if isinstance(ex,HTTPException):
code = ex.code
message = ex.description
status_code = 10001
return APIException(code= code,message=message,status_code=status_code)
# 所有异常 按API异常返回
return APIException
# 专门定义标准化返回的函数--定义程序自己的状态码
# status_code 程序定义的状态码
# 10000 表示成功 逻辑处理请求都成功
# 10001 表示用户不存在
# data 要返回的数据
# message 接口转态的说明
def generate_response(data = None,message = "ok",status_code=10000):
if data is None:
data = []
return {
"status_code":status_code,
"message":message,
"data":data
}
使用SQLAlchemy模块实例化数据对象,ORM(Object Relational Mapping,对象关系映射)可以绕过SQL语句,把数据库的table(表)映射为编程语言的class(类),可以直接使用编程语言的对象模型操作数据库,而不使用SQL语句。
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
# 运行当前下的userdb.py
from . import userdb
# 接口
def init_db_app(app):
db.init_app(app)
# 会自动运行user的表,会自动创建
# db.create_all(app=app)
创建数据库,一对一数据库,一对多数据库,多对多数据库,单数据库
from model import db
from werkzeug.security import generate_password_hash
import datetime
# 类----》表
# 对象---》表中一个记录
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
username = db.Column(db.String(256),nullable=False)
age = db.Column(db.Integer,nullable=False)
hobby = db.Column(db.String(256))
# 自定义序列化工具--把数据库对象装换成字典的形式输出.类里面封装一个函数。
def to_json(self):
return {
"id":self.id,
"username":self.username,
"age":self.age,
"hobby":self.hobby
}
class UserManager(db.Model):
__tablename__ = "usermanager"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
username = db.Column(db.String(256),nullable=False)
# 添加密码项
_password = db.Column("password",db.String(512),nullable=False)
useremail = db.Column(db.String(128),nullable=False,unique=True)
usermobile = db.Column(db.String(11))
create_at = db.Column(db.DateTime(),default=datetime.datetime.now())
role = db.Column(db.Integer,default=1,)
@property #把方法变成属性,生成setter
def password(self):
return self._password
@password.setter #设置数据
def password(self, value):
self._password = generate_password_hash(value)
@classmethod
def create_user(cls,username,userpass,useremail,usermobile):
user = cls()
user.username = username
user.password = userpass
user.useremail = useremail
user.usermobile = usermobile
db.session.add(user)
db.session.commit()
# 自动创建这个表
# 学生信息表
class User2(db.Model):
__tablename__ = "user2"
student_id = db.Column(db.Integer,primary_key=True,autoincrement=True)
student_name = db.Column(db.String(256),nullable=False)
student_age = db.Column(db.Integer,nullable=False)
student_sex = db.Column(db.String(256))
# 创建关系stugrade可以连接StudentGrade里的记录
# 一对一令uselist=False
stugrade = db.relationship("StudentGrade",backref="student")
# 学生成绩表
class StudentGrade(db.Model):
__tablename__ = "student_grade"
id = db.Column(db.Integer,primary_key=True,autoincrement=True)
stu_id = db.Column(db.ForeignKey("user2.student_id"))
subject = db.Column(db.String(256),nullable=False)
grade = db.Column(db.Integer,nullable=False)
# 多对多关系映射
# 运维人员表
class Ops(db.Model):
__tablename__ = "ops"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
ops_name = db.Column(db.String(256),nullable=False)
# 中间表
ops_app = db.Table("ops_app",
db.Column("ops_id",db.ForeignKey("ops.id")),
db.Column("app_id",db.ForeignKey("application.id")))
# 运维应用表
class Application(db.Model):
__tablename__ = "application"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
app_name = db.Column(db.String(256),nullable=False)
ops = db.relationship("Ops",secondary=ops_app,backref="app")
做转发,并把蓝图绑定到核心对象
from .student import stu_bp
from .student import stu_bpa
from .user import user_bp
from .studentapi import stuapi_bp
from .userapi import userapi_bp
# 把蓝图绑定到核心对象
def init_app(app):
app.register_blueprint(stu_bp)
app.register_blueprint(stu_bpa)
app.register_blueprint(user_bp)
app.register_blueprint(stuapi_bp)
app.register_blueprint(userapi_bp)
具体的函数,路由地址,还有映射的认证,Restful Api的接口风格
from flask import Blueprint,request
from model.userdb import User2
from model import db
from libs.response import generate_response
from flask_restful import Resource,Api
from libs.auth import auth
from libs.handler import default_error_handler
# 蓝图1
stu_bp = Blueprint("student", __name__)
# 创建蓝图和视图函数并把视图函数绑定到蓝图1和2(蓝图stu_bp),同下
@stu_bp.route("/student/get")
def get_student():
return "this is get_student"
# 使用蓝图管理 endpoint默认都是(蓝图的名字.函数名)。endpoint全局唯一,否则会抛出断言异常。
# 蓝图2
stu_bpa = Blueprint("sc", __name__ , url_prefix="/v2/")
@stu_bpa.route("/student/sc",methods = ["POST","PUT","PATCH","GET"])
def index():
return "hello world"
# 使用restful,蓝图绑定api
api = Api(stu_bp)
# 把管理异常处理的函数变成自定义的函数
api.handle_error = default_error_handler
# 装饰器添加接口认证
# def login_require(func):
# def inner(*args,**kwargs):
# if request.args.get("username"):
# return func(*args,**kwargs)
# else:
# return generate_response(status_code=10001,message="not allow")
# return inner
class Stu(Resource):
# @stu_bp.route('/stu/get',methods = ["GET"])
# #API接口
# @login_require
# 当执行auth.login_required时,会自动执行auth.verify_password对应的函数
@auth.login_required
def get(self, id=None):
stuid = request.args.get("student_id")
# name = request.arg.get("student_name")
tmp = []
# if name:
# usr = User2.query.filter(User2.student_name.contains(name).all())
# tmp += usr
if stuid is not None:
stu = User2.query.get(stuid)
if stu is None:
return generate_response(message="stuid can not found",
status_code=10001)
tmp.append(stu)
elif id:
stu = User2.query.get(id)
tmp.append(stu)
else:
tmp = User2.query.all()
if not tmp:
return generate_response(message="users can not found",
status_code=10001)
result = [
{
"student_name": stu.student_name,
"student_sex": stu.student_sex,
"student_age": stu.student_age,
"grade":{ i.subject:i.grade for i in stu.stugrade}
}if stu.stugrade else{
"student_name": stu.student_name,
"student_sex": stu.student_sex,
"student_age": stu.student_age,
}for stu in tmp
]
return generate_response(message="get student success",
data=result)
# @stu_bp.route('/stu/add',methods = ["POST"])
def post(self):
user_info = request.json
user = User2(
student_name = user_info.get("student_name"),
student_sex = user_info.get("student_sex"),
student_age = user_info.get("student_age")
)
db.session.add(user)
db.session.commit()
return generate_response(message="add user success")
# @stu_bp.route("/stu/modify/",methods = ["PUT","POST"])
def put(self,id):
stu = User2.query.get(id)
if stu is not None:
stu_info = request.json
stu.student_name = stu_info.get("student_name"),
stu.student_sex = stu_info.get("student_sex"),
stu.student_age = stu_info.get("student_age")
db.session.add(stu)
db.session.commit()
return generate_response(message="update stu success!")
else:
return generate_response(message="stu not found",
status_code=10002)
# @stu_bp.route("/stu/delete/",methods = ["DELETE"])
def delete(self,id):
stu = User2.query.get(id)
if stu is not None:
db.session.delete(stu)
db.session.commit()
return generate_response(message="delete stu success")
else:
return generate_response(message="deletestu not found",
status_code=10003)
api.add_resource(Stu,"/stu")
api.add_resource(Stu,"/stu/",endpoint='stuapiid')
后续将结合数据库收集的日志信息供给celery做分析告警...