美文网首页
08-Flask之淘票票(前后端分离)

08-Flask之淘票票(前后端分离)

作者: 郑元吉 | 来源:发表于2018-11-14 19:09 被阅读20次

    一、区域选择模块

    • 数据库建模
    from App.ext import db
    
    # 字母模型类
    class Letter(db.Model):
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        name = db.Column(db.String(2))
        citys = db.relationship('City', backref='letter', lazy=True)
    
    # 城市模型类
    class City(db.Model):
        # 注意,不是自增长
        id = db.Column(db.Integer, primary_key=True)
        regionName = db.Column(db.String(100))
        cityCode = db.Column(db.String(10))
        pinYin = db.Column(db.String(10))
        c_letter = db.Column(db.Integer, db.ForeignKey(Letter.id))
    

    备注: https://dianying.taobao.com/cityAction.json?activityId&_ksTS=1531740557472_417&jsoncallback=jsonp418&action=cityAction&n_s=new&event_submit_doGetAllRegion=true

    • 数据导入(数据库操作)
    # 从JSON到到数据库脚本 city-mysql.py
    import json
    import pymysql
    
    # 链接数据库
    db = pymysql.Connect(host="localhost", port=3306, user="root", password="123456", database="Tpp", charset="utf8")
    # 数据库游标
    cursor = db.cursor()
    
    # 打开文件
    with open('city.json', 'r') as f:
        # json形式加载
        city_collection = json.load(f)
    
        # 获取所有的键
        returnValue = city_collection.get('returnValue')
        letters = returnValue.keys()
    
        # 遍历插入到数据库中
        for letter in letters:
            # 游标,执行SQL语句 (注意values中的值是字符串)
            db.begin()
            cursor.execute("insert into letter(name) values('{}')".format(letter))
            db.commit()
    
            # 获取字母对应的主键
            db.begin()
            cursor.execute("select id from letter where name='{}';".format(letter))
            db.commit()
            result = cursor.fetchone()
            letter_id = result[0]
    
            # 获取key对应的value
            citys = returnValue.get(letter)
            for c_obj in citys:
                # insert into city(regionName,cityCode,pinYin,c_letter)
                regionName = c_obj.get('regionName')
                cityCode = c_obj.get('cityCode')
                pinYin = c_obj.get('pinYin')
    
                db.begin()
                cursor.execute("insert into city(regionName,cityCode,pinYin,c_letter) values('{}','{}','{}',{});".format(regionName,cityCode,pinYin,letter_id))
                db.commit()
    

    开始使用数据库时,驱动程序会发出一个BEGIN之后COMMIT,符合规范的pythonDBAPI始终以这种方式工作.

    • 返回JSON数据
    {
        'status':200,
        'msg': '获取城市列表数据成功',
        "data":{
            "A":[
                {
                    "id":3643,
                    "parentId":0,
                    "regionName":"阿坝",
                    "cityCode":513200,
                    "pinYin":"ABA"
                },
                {
                    "id":3090,
                    "parentId":0,
                    "regionName":"阿克苏",
                    "cityCode":652901,
                    "pinYin":"AKESU"
                }],
            "B":[
                {
                    "id":3643,
                    "parentId":0,
                    "regionName":"阿坝",
                    "cityCode":513200,
                    "pinYin":"ABA"
                },
                {
                    "id":3090,
                    "parentId":0,
                    "regionName":"阿克苏",
                    "cityCode":652901,
                    "pinYin":"AKESU"
                }]
            ...
    }
    

    @marshal_with()装饰器,而在flask-RESTful文档中高级:嵌套字段并没有使用装饰器,而是通过函数调用的方式实现格式化输出的

    二、用户系统分析

    • 字段
    用户名
        密码
        邮箱
        手机号
        用户状态(是否激活)
        用户权限
        用户token
        头像
        逻辑删除
    
    • 业务流程
    用户名
        密码
        邮箱
            发一个邮件,点击激活
            不激活权限会被限制
    
    用户注册业务.png

    三、用户注册

    • 数据模型(建模)
    class User(db.Model):
        # 主键
        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        # 用户名
        name = db.Column(db.String(30), unique=True)
        # 密码
        password = db.Column(db.Integer(255))
        # 邮箱
        email = db.Column(db.String(30), unique=True)
        # 手机
        iphone = db.Column(db.String(20))
        # 头像
        icon = db.Colum(db.String(100), default='head.png')
        # 是否激活
        is_active = db.Column(db.Boolean, default=False)
        # 用户令牌
        token = db.Column(db.String(255))
        # 权限 
        permissions = db.Column(db.Integer, default=1)
        # 是否被删除
        is_delete = db.Column(db.Boolean, default=False)
    

    RESTful前后端分离,而要给移动端写接口,移动端是没有cookie的,就是用token来作代替方案。

    • 注册接口
    """ 注册接口数据
    {
        "returnCode": "0",
        "returnValue": {
            "token": "8f715ea6-62c5-45a1-9dab-4367f1bf24a5",
            "username": "MM",
            "permissions": "1"
        },
        "status": "200",
        "err": "None"
    }
    """
    
    # 请求参数格式
    parser = reqparse.RequestParser()
    parser.add_argument('username', type=str, required=True, help='请提供用户名')
    parser.add_argument('password', type=str, required=True, help='请提供密码')
    parser.add_argument('email', type=str, required=True, help='请提供邮箱')
    parser.add_argument('iphone', type=str, required=True, help='请提供手机号码')
    
    
    
    # 定义格式的需求,可以继承 fields.Raw 类并且实现格式化函数
    class IconForm(fields.Raw): 
        def format(self, value):
            return '/static/img/' + value
    
    # 输出格式
    user_fields = {
        'username': fields.String,
        'token': fields.String,
        'permissions': fields.String,
        'icon': IconForm(attribute='icon')  # attribute='对应key'
    }
    
    result_fields = {
        'returnCode': fields.String,
        'returnValue': fields.Nested(user_fields),
        'status': fields.String,
        'err': fields.String(default='None')
    }
    
    # 用户注册接口
    class RegisterUser(Resource):
        @marshal_with(result_fields)
        def post(self):
            parse = parser.parse_args()
    
            user = User()
            user.username = parse.get('username')
            user.password = parse.get('password')
            user.email = parse.get('email')
            user.iphone = parse.get('iphone')
            user.token = str(uuid.uuid4())
            print(user.username)
    
            data = {
                'returnCode': '0',
                'returnValue': user,
                'status': '200'
            }
    
            try:
                print(user.token)
                db.session.add(user)
                db.session.commit()
                print('hello')
                return data
            except Exception as e:
                data['err'] = '用户已经存在!'
                data['returnValue'] = None
                data['status'] = '406'
                return data
    

    四、flask-mail插件

    - 安装
        pip install flask-mail
        
    - 配置(app.config配置)
        MAIL_SERVER = "smtp.163.com"
        MAIL_USERNAME = "xxxxxx@163.com"
        MAIL_PASSWORD = "xxxxxx"
        
    - 初始化
        from flask_mail import Mail
        mail = Mail()
        mail.init_app(app)
    
    - 使用
        # 邮件信息
        msg = Message(subject="Tpp激活邮件",        # 主题
                      recipients=[user.email],      # 收件人
                      sender="xxxxxxx@163.com") # 发件人
        # 传入网页(即主体内容,可以为空)
        body_html = render_template('active.html', username=user.username,active_url='http://localhost:5000/api/v1/useractive?token='+user.token)
        msg.html = body_html
        # 发送邮件
        mail.send(msg)
    

    MAIL_PASSWORD密码设置,可以在官网中设置客户端授权密码开启,即可以不使用登录密码!

    五、用户激活

    用户激活,其实也就是一个接口。
    这个接口可以根据链接找到对应用户,并修改用户的状态。
    可以该{token:userId}存储信息。
    
    
    (注册接口)
    # 注册请求
    # 获取用户信息
    # 存储数据库
    # token:userid 存储cache[超时设置]
        cache.set(user.token, user.id, timeout=60)
    # 发送邮件
    
    
    (激活接口)
    # 激活请求
    # 获取用户token
    # 根据token在cache中获取对应的userid
        userid = cache.get(token)
    # 删除token
        cache.delete(token)
    # 根据userid找到对应用户对象
    # 修改用户状态
    # 保存到数据库
    

    redis缓存{token:userId}就可以使用flask-cache,它可以缓存视图,也可以直接使用原生操作用于存取数据。

    用户激活.png

    六、用户登录

    # 登录请求
    # 获取用户名、密码
    # 根据用户名和密码验证
         users = User.query.filter(User.username==username).filter(User.password==password)
         if users.count()>0:    # 账号密码正确
    # 再验证是否激活
    # 返回数据
        成功,返回用户信息(用户名、token...)
        失败,返回用户名或密码错误提示
    

    七、密码安全模块

    generate_password_hash(password): 输入相同,但每次输出结果都是不一样的
    check_password_hash(hash,password): 出入hash与输入的值比较是否相等
    

    八、用户修改密码

    # 修改密码请求
    # 获取token、旧密码、新密码
    # 根据token获取用户信息
    # 验证操作
        旧密码一致,修改
        旧密码不一致,不修改
    # 返回数据
    

    九、用户权限

    # 权限设计与限制
        0 未登陆
            列表A(预览权限)
        1 普通用户
            列表A + 列表B(预览权限)
        2 会员
            列表A + 列表B
        4 超级会员  
            列表A + 列表B + 下载权限
            
    # 资源限制
        if user.permissions == 1:
            return {'msg': '麻麻地啦', 'data': ' 列表A + 列表B(预览权限)'}
        elif user.permissions == 2:
            return {'msg': '会员,奔小康水平', 'data': ' 列表A + 列表B'}
        elif user.permissions == 4:
            return {'msg': '超级会员,请叫我土豪', 'data': ' 列表A + 列表B + 下载'}
    

    十、自定义权限(装饰器)

    # 很多资源都有权限问题,那都会需要上述判断处理
    # 类似接口Blueprint定义接口时,通过装饰器实现统一
    # 添加一个权限装饰器,给需要权限限制的加上装饰器即可
    
    
    - Linux文件读写权限 
        r 》 4 》 100
        w 》 2 》 010
        x 》 1 》 001
        
        6表示有读写权限 》 110 》 
            判断是否有读权限?      位运算: 110 & 100  》 100  》 r
            判断是否有可执行权限?   位运算: 110 & 001  》 000  》 无 
            
    - 装饰器
    # 权限管理装饰器  [只管有无权限,什么数据不管]
    def check_permissions_control(permissions):
        def check_permissions(func):
            def check(*args, **kwargs):
                parse = parser.parse_args()
                token = parse.get('token')
                if token:  # 验证token
                    users = User.query.filter(User.token == token)
                    if users.count() > 0:  # 有用户
                        user = users.first()
                        if user.permissions & permissions == permissions:   # 有权限
                            # 权限,即执行装饰的函数,否则报错跳出
                            return func(*args, **kwargs)
                        else:
                            abort(403,message='你没有操作权限,请联系管理员')
                    else:  # 未登录
                        abort(401, message='你还没登录,请登录后操作')
                else:  # 未登录
                    abort(401, message='你还没登录,请登录后操作')
            return check
        return check_permissions
    

    按位与: &
    按位或: |

    if user.permissions & permissions == permissions: 权限判断

    十一、电影信息接口+权限管理

    • 电影信息接口
    - 数据库结构
    - 模型结构
    - 插入数据(数据库)
    - 定义接口
    - 参数设置
        flag: 0 全部
        flag: 1 热映
        flag: 2 即将上映
        flag = parse.get('flag') or 0 
    - 返回数据
    
    • 添加电影接口(权限管理)
    - 权限判断(添加装饰器即可)
        通过上述装饰器方式,处理权限
        有权限,才会调用post接口的函数处理
    - post接口(只管数据)
        获取数据
        存入数据库
        返回数据
    

    十二、电影院信息接口

    - 数据库结构
    - 模型结构
    - 插入数据
    - 定义接口
    - 参数设置
        city: 城市
        district: 地区
        sort: 排序
        limit: 显示条数 
    - 返回数据
    

    十三、图片上传

    # settings.py文件中
        BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        UPLOAD_FOLDER = os.path.join(BASE_DIR, 'App/static/img/')
    
    # UploadFile.py文件上传api
    parser = reqparse.RequestParser()
    parser.add_argument('token', type=str, required=True, help='缺少token')
    parser.add_argument('headimg', type=werkzeug.datastructures.FileStorage, location='files',required=True, help='请选择图片')
    
    class UserHeadResource(Resource):
        @marshal_with(result_fields)
        def post(self):
            parse = parser.parse_args()
            token = parse.get('token')
    
            returndata = {}
    
            users = User.query.filter(User.token == token)
            if users.count()>0:
    
                user = users.first()
    
                # 图片数据
                imgfile = parse.get('headimg')
                # 图片名称 secure_filename(imgFile.filename)
                filename = '%d-%s' % (user.id,secure_filename(imgfile.filename))
                # 图片路径
                filepath = os.path.join(UPLOAD_FOLDER, filename)
                # 保存文件
                imgfile.save(filepath)
    
                # 保存到数据库
                user.icon = filename
                db.session.add(user)
                db.session.commit()
    
                # 返回数据
                returndata['status'] = 200
                returndata['msg'] = '文件上传成功'
                returndata['data'] = user
    
                return returndata
    
            else:
                returndata['status'] = 401
                returndata['msg'] = '上传文件失败'
                returndata['err'] = 'token错误'
    
                return returndata
    

    备注: img目录需要有!

    十四、项目依赖问题

    requirements.txt 文件 里面记录了当前程序的所有依赖包及其精确版本号。
    其作用是用来在另一台PC上重新构建项目所需要的运行环境依赖。

    - 生成requirements.txt
        pip freeze > requirements.txt
    - 安装requirements.txt依赖
        pip install -r requirements.txt
    

    相关文章

      网友评论

          本文标题:08-Flask之淘票票(前后端分离)

          本文链接:https://www.haomeiwen.com/subject/aotsfqtx.html