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

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

  • 數(shù)據(jù)庫建模
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ù)庫操作)
# 從JSON到到數(shù)據(jù)庫腳本 city-mysql.py
import json
import pymysql

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

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

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

    # 遍歷插入到數(shù)據(jù)庫中
    for letter in letters:
        # 游標(biāo),執(zhí)行SQL語句 (注意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()

開始使用數(shù)據(jù)庫時(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í):嵌套字段并沒有使用裝飾器,而是通過函數(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è)業(yè)務(wù)

三、用戶注冊(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)端寫接口蝌戒,移動(dòng)端是沒有cookie的,就是用token來作代替方案欠肾。

  • 注冊(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)頁(即主體內(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)密碼開啟,即可以不使用登錄密碼!

五瑟慈、用戶激活

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


(注冊(cè)接口)
# 注冊(cè)請(qǐng)求
# 獲取用戶信息
# 存儲(chǔ)數(shù)據(jù)庫
# 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ù)庫

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)限問題墩弯,那都會(huì)需要上述判斷處理
# 類似接口Blueprint定義接口時(shí),通過裝飾器實(shí)現(xiàn)統(tǒng)一
# 添加一個(gè)權(quán)限裝飾器寞射,給需要權(quán)限限制的加上裝飾器即可


- Linux文件讀寫權(quán)限 
    r 》 4 》 100
    w 》 2 》 010
    x 》 1 》 001
    
    6表示有讀寫權(quán)限 》 110 》 
        判斷是否有讀權(quán)限?      位運(yùn)算: 110 & 100  》 100  》 r
        判斷是否有可執(zhí)行權(quán)限?   位運(yùn)算: 110 & 001  》 000  》 無 
        
- 裝飾器
# 權(quán)限管理裝飾器  [只管有無權(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='你沒有操作權(quán)限,請(qǐng)聯(lián)系管理員')
                else:  # 未登錄
                    abort(401, message='你還沒登錄,請(qǐng)登錄后操作')
            else:  # 未登錄
                abort(401, message='你還沒登錄,請(qǐng)登錄后操作')
        return check
    return check_permissions

按位與: &
按位或: |

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

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

  • 電影信息接口
- 數(shù)據(jù)庫結(jié)構(gòu)
- 模型結(jié)構(gòu)
- 插入數(shù)據(jù)(數(shù)據(jù)庫)
- 定義接口
- 參數(shù)設(shè)置
    flag: 0 全部
    flag: 1 熱映
    flag: 2 即將上映
    flag = parse.get('flag') or 0 
- 返回?cái)?shù)據(jù)
  • 添加電影接口(權(quán)限管理)
- 權(quán)限判斷(添加裝飾器即可)
    通過上述裝飾器方式梁丘,處理權(quán)限
    有權(quán)限侵浸,才會(huì)調(diào)用post接口的函數(shù)處理
- post接口(只管數(shù)據(jù))
    獲取數(shù)據(jù)
    存入數(shù)據(jù)庫
    返回?cái)?shù)據(jù)

十二、電影院信息接口

- 數(shù)據(jù)庫結(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ù)庫
            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)目依賴問題

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

- 生成requirements.txt
    pip freeze > requirements.txt
- 安裝requirements.txt依賴
    pip install -r requirements.txt
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市杨何,隨后出現(xiàn)的幾起案子酱塔,更是在濱河造成了極大的恐慌,老刑警劉巖危虱,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羊娃,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡埃跷,警方通過查閱死者的電腦和手機(jī)蕊玷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弥雹,“玉大人垃帅,你說我怎么就攤上這事∶逶悖” “怎么了挺智?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)窗宦。 經(jīng)常有香客問我赦颇,道長(zhǎng),這世上最難降的妖魔是什么赴涵? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任媒怯,我火速辦了婚禮,結(jié)果婚禮上髓窜,老公的妹妹穿的比我還像新娘扇苞。我一直安慰自己欺殿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布鳖敷。 她就那樣靜靜地躺著推正,像睡著了一般器虾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天顷扩,我揣著相機(jī)與錄音宪赶,去河邊找鬼痛单。 笑死塑顺,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的畅哑。 我是一名探鬼主播肴楷,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼荠呐!你這毒婦竟也來了赛蔫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤直秆,失蹤者是張志新(化名)和其女友劉穎濒募,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圾结,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瑰剃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筝野。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晌姚。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歇竟,靈堂內(nèi)的尸體忽然破棺而出挥唠,到底是詐尸還是另有隱情,我是刑警寧澤焕议,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布宝磨,位于F島的核電站,受9級(jí)特大地震影響盅安,放射性物質(zhì)發(fā)生泄漏唤锉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一别瞭、第九天 我趴在偏房一處隱蔽的房頂上張望窿祥。 院中可真熱鬧,春花似錦蝙寨、人聲如沸晒衩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽听系。三九已至贝奇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間跛锌,已是汗流浹背弃秆。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留髓帽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓脑豹,卻偏偏與公主長(zhǎng)得像郑藏,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瘩欺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容