Flask框架的web开发02(web项目整体架构)

目录

一: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框架整体构造

1.介绍

        flask框架实现web平台开发,其中包含config配置,forms验证相关,libs公共库,mifrations同步数据库表单,model数据库,route路由函数,app.py实例化核心对象,manager.py迁移脚本和管理工具,server.py启动程序。

2.构造图 

Flask框架的web开发02(web项目整体架构)_第1张图片

二,核心对象、管理、启动模块 

1.核心对象app.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

2.管理模块manager.py

主要工作:使用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()

3.启动模块server.py

主要工作:把上下文管理器对象从栈对拉去下来,并指定配置文件内容

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

 四:forms验证模块

验证分为注册验证和登录验证,注册模块根据客户端传过来的字段是否符合要求进行验证,登录模块需要通过用户数据表对比验证。

# 验证相关,验证与客户端传递过来的有关数据
# 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("验证失败!")

五:libs公共库模块

1.auth权限认证模块

使用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)

2.error_code认证状态异常模块

使用的是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

 3.handler重写处理异常函数

主要目的是使异常处理按照统一的标准进行

# 处理异常的函数
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

 4.response定义标准化返回

# 专门定义标准化返回的函数--定义程序自己的状态码
# 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
    }

六:model数据库模块

1.__init__.py

        使用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)

 2.userdb.py数据库模块

创建数据库,一对一数据库,一对多数据库,多对多数据库,单数据库

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")

 七:route路由模块

1.__init__.py

做转发,并把蓝图绑定到核心对象

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)

2.student.py

具体的函数,路由地址,还有映射的认证,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')

 八:回顾总体web项目流程

Flask框架的web开发02(web项目整体架构)_第2张图片

 后续将结合数据库收集的日志信息供给celery做分析告警...

你可能感兴趣的:(python,flask,python,后端,架构)