一剩辟、區(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))
- 數(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