欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-27 07:52:44
...

一、区域选择模块

  • 数据库建模
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
    头像
    逻辑删除
  • 业务流程
用户名
    密码
    邮箱
        发一个邮件,点击**
        不**权限会被限制
08-Flask之淘票票(前后端分离)
用户注册业务

三、用户注册

  • 数据模型(建模)
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 = "aaa@qq.com"
    MAIL_PASSWORD = "xxxxxx"
    
- 初始化
    from flask_mail import Mail
    mail = Mail()
    mail.init_app(app)

- 使用
    # 邮件信息
    msg = Message(subject="Tpp**邮件",        # 主题
                  recipients=[user.email],      # 收件人
                  sender="aaa@qq.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,它可以缓存视图,也可以直接使用原生操作用于存取数据。

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

六、用户登录

# 登录请求
# 获取用户名、密码
# 根据用户名和密码验证
     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