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

一、區(qū)域選擇模塊

  • 數(shù)據(jù)庫(kù)建模
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):
    # 注意,不是自增長(zhǎng)
    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

  • 數(shù)據(jù)導(dǎo)入(數(shù)據(jù)庫(kù)操作)
# 從JSON到到數(shù)據(jù)庫(kù)腳本 city-mysql.py
import json
import pymysql

# 鏈接數(shù)據(jù)庫(kù)
db = pymysql.Connect(host="localhost", port=3306, user="root", password="123456", database="Tpp", charset="utf8")
# 數(shù)據(jù)庫(kù)游標(biāo)
cursor = db.cursor()

# 打開(kāi)文件
with open('city.json', 'r') as f:
    # json形式加載
    city_collection = json.load(f)

    # 獲取所有的鍵
    returnValue = city_collection.get('returnValue')
    letters = returnValue.keys()

    # 遍歷插入到數(shù)據(jù)庫(kù)中
    for letter in letters:
        # 游標(biāo),執(zhí)行SQL語(yǔ)句 (注意values中的值是字符串)
        db.begin()
        cursor.execute("insert into letter(name) values('{}')".format(letter))
        db.commit()

        # 獲取字母對(duì)應(yīng)的主鍵
        db.begin()
        cursor.execute("select id from letter where name='{}';".format(letter))
        db.commit()
        result = cursor.fetchone()
        letter_id = result[0]

        # 獲取key對(duì)應(yīng)的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()

開(kāi)始使用數(shù)據(jù)庫(kù)時(shí),驅(qū)動(dòng)程序會(huì)發(fā)出一個(gè)BEGIN之后COMMIT,符合規(guī)范的pythonDBAPI始終以這種方式工作.

  • 返回JSON數(shù)據(jù)
{
    'status':200,
    'msg': '獲取城市列表數(shù)據(jù)成功',
    "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文檔中高級(jí):嵌套字段并沒(méi)有使用裝飾器,而是通過(guò)函數(shù)調(diào)用的方式實(shí)現(xiàn)格式化輸出的

二、用戶系統(tǒng)分析

  • 字段
    用戶名
    密碼
    郵箱
    手機(jī)號(hào)
    用戶狀態(tài)(是否激活)
    用戶權(quán)限
    用戶token
    頭像
    邏輯刪除

  • 業(yè)務(wù)流程
    用戶名
    密碼
    郵箱
        發(fā)一個(gè)郵件,點(diǎn)擊激活
        不激活權(quán)限會(huì)被限制

三、用戶注冊(cè)

  • 數(shù)據(jù)模型(建模)
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)
    # 手機(jī)
    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))
    # 權(quán)限 
    permissions = db.Column(db.Integer, default=1)
    # 是否被刪除
    is_delete = db.Column(db.Boolean, default=False)

RESTful前后端分離,而要給移動(dòng)端寫(xiě)接口,移動(dòng)端是沒(méi)有cookie的,就是用token來(lái)作代替方案。

  • 注冊(cè)接口
""" 注冊(cè)接口數(shù)據(jù)
{
    "returnCode": "0",
    "returnValue": {
        "token": "8f715ea6-62c5-45a1-9dab-4367f1bf24a5",
        "username": "MM",
        "permissions": "1"
    },
    "status": "200",
    "err": "None"
}
"""

# 請(qǐng)求參數(shù)格式
parser = reqparse.RequestParser()
parser.add_argument('username', type=str, required=True, help='請(qǐng)?zhí)峁┯脩裘?)
parser.add_argument('password', type=str, required=True, help='請(qǐng)?zhí)峁┟艽a')
parser.add_argument('email', type=str, required=True, help='請(qǐng)?zhí)峁┼]箱')
parser.add_argument('iphone', type=str, required=True, help='請(qǐng)?zhí)峁┦謾C(jī)號(hào)碼')

# 定義格式的需求,可以繼承 fields.Raw 類并且實(shí)現(xiàn)格式化函數(shù)
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='對(duì)應(yīng)key'
}

result_fields = {
    'returnCode': fields.String,
    'returnValue': fields.Nested(user_fields),
    'status': fields.String,
    'err': fields.String(default='None')
}

# 用戶注冊(cè)接口
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'] = '用戶已經(jīng)存在!'
            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") # 發(fā)件人
    # 傳入網(wǎng)頁(yè)(即主體內(nèi)容,可以為空)
    body_html = render_template('active.html', username=user.username,active_url='http://localhost:5000/api/v1/useractive?token='+user.token)
    msg.html = body_html
    # 發(fā)送郵件
    mail.send(msg)

MAIL_PASSWORD密碼設(shè)置,可以在官網(wǎng)中設(shè)置客戶端授權(quán)密碼開(kāi)啟,即可以不使用登錄密碼!

五、用戶激活

用戶激活,其實(shí)也就是一個(gè)接口。
這個(gè)接口可以根據(jù)鏈接找到對(duì)應(yīng)用戶,并修改用戶的狀態(tài)。
可以該{token:userId}存儲(chǔ)信息。

(注冊(cè)接口)
# 注冊(cè)請(qǐng)求
# 獲取用戶信息
# 存儲(chǔ)數(shù)據(jù)庫(kù)
# token:userid 存儲(chǔ)cache[超時(shí)設(shè)置]
    cache.set(user.token, user.id, timeout=60)
# 發(fā)送郵件

(激活接口)
# 激活請(qǐng)求
# 獲取用戶token
# 根據(jù)token在cache中獲取對(duì)應(yīng)的userid
    userid = cache.get(token)
# 刪除token
    cache.delete(token)
# 根據(jù)userid找到對(duì)應(yīng)用戶對(duì)象
# 修改用戶狀態(tài)
# 保存到數(shù)據(jù)庫(kù)

redis緩存{token:userId}就可以使用flask-cache,它可以緩存視圖,也可以直接使用原生操作用于存取數(shù)據(jù)。

六、用戶登錄

# 登錄請(qǐng)求
# 獲取用戶名、密碼
# 根據(jù)用戶名和密碼驗(yàn)證
     users = User.query.filter(User.username==username).filter(User.password==password)
     if users.count()>0:    # 賬號(hào)密碼正確
# 再驗(yàn)證是否激活
# 返回?cái)?shù)據(jù)
    成功,返回用戶信息(用戶名、token...)
    失敗,返回用戶名或密碼錯(cuò)誤提示

七、密碼安全模塊

generate_password_hash(password): 輸入相同,但每次輸出結(jié)果都是不一樣的
check_password_hash(hash,password): 出入hash與輸入的值比較是否相等

八、用戶修改密碼

# 修改密碼請(qǐng)求
# 獲取token、舊密碼、新密碼
# 根據(jù)token獲取用戶信息
# 驗(yàn)證操作
    舊密碼一致,修改
    舊密碼不一致,不修改
# 返回?cái)?shù)據(jù)

九、用戶權(quán)限

# 權(quán)限設(shè)計(jì)與限制
    0 未登陸
        列表A(預(yù)覽權(quán)限)
    1 普通用戶
        列表A + 列表B(預(yù)覽權(quán)限)
    2 會(huì)員
        列表A + 列表B
    4 超級(jí)會(huì)員  
        列表A + 列表B + 下載權(quán)限

# 資源限制
    if user.permissions == 1:
        return {'msg': '麻麻地啦', 'data': ' 列表A + 列表B(預(yù)覽權(quán)限)'}
    elif user.permissions == 2:
        return {'msg': '會(huì)員,奔小康水平', 'data': ' 列表A + 列表B'}
    elif user.permissions == 4:
        return {'msg': '超級(jí)會(huì)員,請(qǐng)叫我土豪', 'data': ' 列表A + 列表B + 下載'}

十、自定義權(quán)限(裝飾器)

# 很多資源都有權(quán)限問(wèn)題,那都會(huì)需要上述判斷處理
# 類似接口Blueprint定義接口時(shí),通過(guò)裝飾器實(shí)現(xiàn)統(tǒng)一
# 添加一個(gè)權(quán)限裝飾器,給需要權(quán)限限制的加上裝飾器即可

- Linux文件讀寫(xiě)權(quán)限 
    r 》 4 》 100
    w 》 2 》 010
    x 》 1 》 001

    6表示有讀寫(xiě)權(quán)限 》 110 》 
        判斷是否有讀權(quán)限?      位運(yùn)算: 110 & 100  》 100  》 r
        判斷是否有可執(zhí)行權(quán)限?   位運(yùn)算: 110 & 001  》 000  》 無(wú) 

- 裝飾器
# 權(quán)限管理裝飾器  [只管有無(wú)權(quán)限,什么數(shù)據(jù)不管]
def check_permissions_control(permissions):
    def check_permissions(func):
        def check(*args, **kwargs):
            parse = parser.parse_args()
            token = parse.get('token')
            if token:  # 驗(yàn)證token
                users = User.query.filter(User.token == token)
                if users.count() > 0:  # 有用戶
                    user = users.first()
                    if user.permissions & permissions == permissions:   # 有權(quán)限
                        # 權(quán)限,即執(zhí)行裝飾的函數(shù),否則報(bào)錯(cuò)跳出
                        return func(*args, **kwargs)
                    else:
                        abort(403,message='你沒(méi)有操作權(quán)限,請(qǐng)聯(lián)系管理員')
                else:  # 未登錄
                    abort(401, message='你還沒(méi)登錄,請(qǐng)登錄后操作')
            else:  # 未登錄
                abort(401, message='你還沒(méi)登錄,請(qǐng)登錄后操作')
        return check
    return check_permissions

按位與: &
按位或: |

if user.permissions & permissions == permissions: 權(quán)限判斷

十一、電影信息接口+權(quán)限管理

  • 電影信息接口
- 數(shù)據(jù)庫(kù)結(jié)構(gòu)
- 模型結(jié)構(gòu)
- 插入數(shù)據(jù)(數(shù)據(jù)庫(kù))
- 定義接口
- 參數(shù)設(shè)置
    flag: 0 全部
    flag: 1 熱映
    flag: 2 即將上映
    flag = parse.get('flag') or 0 
- 返回?cái)?shù)據(jù)

  • 添加電影接口(權(quán)限管理)
- 權(quán)限判斷(添加裝飾器即可)
    通過(guò)上述裝飾器方式,處理權(quán)限
    有權(quán)限,才會(huì)調(diào)用post接口的函數(shù)處理
- post接口(只管數(shù)據(jù))
    獲取數(shù)據(jù)
    存入數(shù)據(jù)庫(kù)
    返回?cái)?shù)據(jù)

十二、電影院信息接口

- 數(shù)據(jù)庫(kù)結(jié)構(gòu)
- 模型結(jié)構(gòu)
- 插入數(shù)據(jù)
- 定義接口
- 參數(shù)設(shè)置
    city: 城市
    district: 地區(qū)
    sort: 排序
    limit: 顯示條數(shù) 
- 返回?cái)?shù)據(jù)

十三、圖片上傳

# 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='請(qǐng)選擇圖片')

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

            # 圖片數(shù)據(jù)
            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)

            # 保存到數(shù)據(jù)庫(kù)
            user.icon = filename
            db.session.add(user)
            db.session.commit()

            # 返回?cái)?shù)據(jù)
            returndata['status'] = 200
            returndata['msg'] = '文件上傳成功'
            returndata['data'] = user

            return returndata

        else:
            returndata['status'] = 401
            returndata['msg'] = '上傳文件失敗'
            returndata['err'] = 'token錯(cuò)誤'

            return returndata

備注: img目錄需要有!

十四、項(xiàng)目依賴問(wèn)題

requirements.txt 文件 里面記錄了當(dāng)前程序的所有依賴包及其精確版本號(hào)。
其作用是用來(lái)在另一臺(tái)PC上重新構(gòu)建項(xiàng)目所需要的運(yùn)行環(huán)境依賴。

- 生成requirements.txt
    pip freeze > requirements.txt
- 安裝requirements.txt依賴
    pip install -r requirements.txt

作者:西門(mén)奄
鏈接:http://www.itdecent.cn/u/77035eb804c3
來(lái)源:簡(jiǎn)書(shū)
簡(jiǎn)書(shū)著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容