目錄
1.模型構造器:ModelSchema
https://marshmallow-sqlalchemy.readthedocs.io/en/latest/
注意:flask_marshmallow在0.12.0版本以後已經移除了ModelSchema和TableSchema這兩個模型構造器類,官方轉而推薦了使用SQLAlchemyAutoSchema和SQLAlchemySchema這2個類,前後兩者用法類似。
1.SQLAlchemySchema
from marshmallow_sqlalchemy import SQLAlchemySchema,SQLAlchemyAutoSchema,auto_field class UserSchema5(SQLAlchemySchema): # auto_field的作用,設定當前資料【欄位的型別】和【選項宣告】自動從模型中對應的欄位中提取 # name = auto_field() # 1.此處,資料庫中根本沒有username,需要在第一個引數位置,宣告當前資料字典的型別和選項宣告從模型的哪個欄位提取的 username = auto_field("name",dump_only=True) # 2.可以在原欄位基礎上面,增加或者覆蓋模型中原來的宣告 created_time = auto_field(format="%Y-%m-%d") # 3.甚至可以宣告一些不是模型的欄位 token = fields.String() class Meta: model = User fields = ["username","created_time","token"] def index5(): """單個模型資料的序列化處理""" from datetime import datetime user1 = User( name="xiaoming", password="123456", age=16, email="333@qq.com", money=31.50, created_time= datetime.now(), ) user1.token = "abc" # 把模型物件轉換成字典格式 data1 = UserSchema5().dump(user1) print(type(data1),data1) return "ok"
總結
1.auto_field用來設定欄位的型別和選項宣告
2.auto_field的第一個引數含義:給欄位設定別名
3.auto_field可以給原欄位增加或覆蓋模型中原來的宣告
4.甚至可以宣告一些非模型內的欄位:直接模型類物件.屬性即可
2.SQLAlchemyAutoSchema
SQLAlchemySchema使用起來,雖然比上面的Schema簡單許多,但是還是需要給轉換的欄位全部統一寫上才轉換這些欄位
如果不想編寫欄位資訊,直接從模型中複製,也可以使用SQLAlchemyAutoSchema。
class UserSchema6(SQLAlchemyAutoSchema): token = fields.String() class Meta: model = User include_fk = False # 啟用外來鍵關係 include_relationships = False # 模型關係外部屬性 fields = ["name","created_time","info","token"] # 如果要全換全部欄位,就不要宣告fields或exclude欄位即可 sql_session = db.session def index(): """單個模型資料的序列化處理""" from datetime import datetime user1 = User( name="xiaoming", password="123456", age=16, email="333@qq.com", money=31.50, created_time= datetime.now(), info=UserProfile(position="助教") ) # 把模型物件轉換成字典格式 user1.token="abcccccc" data1 = UserSchema6().dump(user1) print(type(data1),data1) return "ok"
總結
1.不需要編寫欄位資訊
2.相關設定全部寫在class Meta中
2.註冊功能基本實現
1.關於手機號碼的唯一性驗證
1.驗證手機號碼唯一的介面
在開發中,針對客戶端提交的資料進行驗證或提供模型資料轉換格式成字典給客戶端。可以使用Marshmallow模組來進行。
from application import jsonrpc,db from .marshmallow import MobileSchema from marshmallow import ValidationError from message import ErrorMessage as Message from status import APIStatus as status @jsonrpc.method("User.mobile") def mobile(mobile): """驗證手機號碼是否已經註冊""" ms = MobileSchema() try: ms.load({"mobile":mobile}) # 對使用者在前端輸入的手機號進行反序列化校驗 ret = {"errno":status.CODE_OK, "errmsg":Message.ok} except ValidationError as e: ret = {"errno":status.CODE_VALIDATE_ERROR, "errmsg": e.messages["mobile"][0]} return ret
from marshmallow import Schema,fields,validate,validates,ValidationError from message import ErrorMessage as Message from .models import User class MobileSchema(Schema): '''驗證手機號所使用的序列化器''' # 1.驗證手機號格式是否正確 mobile = fields.String(required=True,validate=validate.Regexp("^1[3-9]\d{9}$",error=Message.mobile_format_error)) # 2.驗證手機號是否已經存在(被註冊過了) @validates("mobile") def validates_mobile(self,data): user = User.query.filter(User.mobile==data).first() if user is not None: raise ValidationError(message=Message.mobile_is_use) return data
狀態碼和配置資訊單獨放到utils的language資料夾中,便於更改
class APIStatus(): CODE_OK = 1000 # 介面操作成功 CODE_VALIDATE_ERROR = 1001 # 驗證有誤!
class ErrorMessage(): ok = "ok" mobile_format_error = "手機號碼格式有誤!" mobile_is_use = "對不起,當前手機已經被註冊!"
Tip:將language作為導包路徑進行使用
為了方便導包,所以我們設定當前language作為導包路徑進行使用.
def init_app(config_path): """全域性初始化""" ...... app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 載入導包路徑 sys.path.insert(0, os.path.join(app.BASE_DIR,"application/utils/language")) ......
2.客戶端傳送請求進行手機號驗證
在客戶端輸入手機號,觸發check_mobile方法 向後端發起請求,進行手機號驗證
<div class="form-item"> <label class="text">手機</label> <input type="text" v-model="mobile" @change="check_mobile" placeholder="請輸入手機號"> </div> <script> methods:{ check_mobile(){ // 驗證手機號碼 this.axios.post("",{ "jsonrpc": "2.0", "id":1, "method": "User.mobile", "params": {"mobile": this.mobile} }).then(response=>{ this.game.print(response.data.result); if(response.data.result.errno != 1000){ api.alert({ title: "錯誤提示", msg: response.data.result.errmsg, }); } }).catch(error=>{ this.game.print(error.response.data.error); }); }, back(){ this.game.goGroup("user",0); } } }) } </script>
function init(){ var game = new Game("../mp3/bg1.mp3"); Vue.prototype.game = game; // 初始化axios axios.defaults.baseURL = "http://192.168.20.251:5000/api" // 服務端api介面閘道器地址 axios.defaults.timeout = 2500; // 請求超時時間 axios.defaults.withCredentials = false; // 跨域請求資源的情況下,忽略cookie的傳送 Vue.prototype.axios = axios; Vue.prototype.uuid = UUID.generate; }
註冊頁面呼叫init函式進行初始化.
html/register.html
<div class="form-item"> <label class="text">手機</label> <input type="text" v-model="mobile" @change="check_mobile" placeholder="請輸入手機號"> </div> <script> apiready = function(){ init(); new Vue({ .... methods:{ check_mobile(){ // 驗證手機號碼 this.axios.post("",{ "jsonrpc": "2.0", "id": this.uuid(), "method": "User.mobile", "params": {"mobile": this.mobile} }).then(response=>{ this.game.print(response.data.result); if(response.data.result.errno != 1000){ api.alert({ title: "錯誤提示", msg: response.data.result.errmsg, }); } }).catch(error=>{ this.game.print(error.response.data.error); }); }, back(){ this.game.goGroup("user",0); } } }) } </script>
2.儲存使用者註冊資訊
1.儲存使用者註冊資訊介面
application.apps.users.marshmallow
from marshmallow import Schema,fields,validate,validates,ValidationError from message import ErrorMessage as Message from .models import User,db class MobileSchema(Schema): ...... from marshmallow_sqlalchemy import SQLAlchemyAutoSchema,auto_field from marshmallow import post_load,pre_load,validates_schema class UserSchema(SQLAlchemyAutoSchema): mobile = auto_field(required=True, load_only=True) password = fields.String(required=True, load_only=True) password2 = fields.String(required=True, load_only=True) sms_code = fields.String(required=True, load_only=True) class Meta: model = User include_fk = True # 啟用外來鍵關係 include_relationships = True # 模型關係外部屬性 fields = ["id", "name","mobile","password","password2","sms_code"] # 如果要全換全部欄位,就不要宣告fields或exclude欄位即可 sql_session = db.session @post_load() def save_object(self, data, **kwargs): # 確認密碼和驗證碼這兩個欄位並不需要存到資料庫中 data.pop("password2") data.pop("sms_code") # 註冊成功後,使用者預設的名稱為自己的手機號 data["name"] = data["mobile"] # 建立User模型類物件 instance = User(**data) # 將註冊資訊儲存到資料庫中 db.session.add( instance ) db.session.commit() return instance @validates_schema def validate(self,data, **kwargs): # 校驗密碼和確認密碼 if data["password"] != data["password2"]: raise ValidationError(message=Message.password_not_match,field_name="password") #todo 校驗簡訊驗證碼 return data
application.apps.users.views
@jsonrpc.method("User.register") def register(mobile,password,password2, sms_code): """使用者資訊註冊""" try: ms = MobileSchema() ms.load({"mobile": mobile}) us = UserSchema() user = us.load({ "mobile":mobile, "password":password, "password2":password2, "sms_code": sms_code }) data = {"errno": status.CODE_OK,"errmsg":us.dump(user)} except ValidationError as e: data = {"errno": status.CODE_VALIDATE_ERROR,"errmsg":e.messages} return data
class ErrorMessage(): ok = "ok" mobile_format_error = "手機號碼格式有誤!" mobile_is_use = "對不起,當前手機已經被註冊!" username_is_use = "對不起,當前使用者名稱已經被使用!" password_not_match = "密碼和驗證密碼不匹配!"
2.使用者點選立即註冊按鈕向後端傳送請求
html/register.html程式碼
使用者點選註冊按鈕,向後端發起請求
1.在使用者點選註冊按鈕向後端傳送請求之前,要對手機號/密碼/確認密碼和驗證碼做前端的校驗,如果出錯了,直接顯示錯誤彈窗
2.當前端驗證通過後,就可以向後端發起請求了。如果成功了,直接顯示跳轉彈窗,失敗了則顯示錯誤彈窗
<div class="form-item"> <label class="text">手機</label> <input type="text" v-model="mobile" @change="check_mobile" placeholder="請輸入手機號"> </div> <div class="form-item"> <img class="commit" @click="registerHandle" src="../static/images/commit.png"/> </div> <script> apiready = function(){ init(); new Vue({ ... methods:{ registerHandle(){ // 註冊處理 this.game.play_music('../static/mp3/btn1.mp3'); // 驗證資料[雙向驗證] if (!/1[3-9]\d{9}/.test(this.mobile)){ api.alert({ title: "警告", msg: "手機號碼格式不正確!", }); return; // 阻止程式碼繼續往下執行 } if(this.password.length<3 || this.password.length > 16){ api.alert({ title: "警告", msg: "密碼長度必須在3-16個字元之間!", }); return; } if(this.password != this.password2){ api.alert({ title: "警告", msg: "密碼和確認密碼不匹配!", }); return; // 阻止程式碼繼續往下執行 } if(this.sms_code.length<1){ api.alert({ title: "警告", msg: "驗證碼不能為空!", }); return; // 阻止程式碼繼續往下執行 } if(this.agree === false){ api.alert({ title: "警告", msg: "對不起, 必須同意磨方的使用者協議和隱私協議才能繼續註冊!", }); return; // 阻止程式碼繼續往下執行 } this.axios.post("",{ "jsonrpc": "2.0", "id": this.uuid(), "method": "User.register", "params": { "mobile": this.mobile, "sms_code":this.sms_code, "password":this.password, "password2":this.password2, } }).then(response=>{ this.game.print(response.data.result); if(response.data.result.errno != 1000){ api.alert({ title: "錯誤提示", msg: response.data.result.errmsg, }); }else{ // 註冊成功! api.confirm({ title: '磨方提示', msg: '註冊成功', buttons: ['返回首頁', '個人中心'] }, (ret, err)=>{ if(ret.buttonIndex == 1){ // 跳轉到首頁 this.game.outGroup("user"); }else{ // 跳轉到個人中心 this.game.goGroup("user",2); } }); } }).catch(error=>{ this.game.print(error.response.data.error); }); }, check_mobile(){ ..... }, back(){ this.game.goGroup("user",0); } } }) } </script>
<script> apiready = function(){ frames: [{ name: 'login', url: './login.html', },{ name: 'register', url: './register.html', },{ name: 'user', url: './user.html', }] } </script>
3.傳送簡訊驗證碼
# 簡訊相關配置 SMS_ACCOUNT_ID = "8a216da8754a45d5017563ac8e8406ff" # 介面主賬號 SMS_ACCOUNT_TOKEN = "a2054f169cbf42c8b9ef2984419079da" # 認證token令牌 SMS_APP_ID = "8a216da8754a45d5017563ac8f910705" # 應用ID SMS_TEMPLATE_ID = 1 # 簡訊模板ID SMS_EXPIRE_TIME = 60 * 5 # 簡訊有效時間,單位:秒/s SMS_INTERVAL_TIME = 60 # 簡訊傳送冷卻時間,單位:秒/s
from application import jsonrpc import re,random,json from status import APIStatus as status from message import ErrorMessage as message from ronglian_sms_sdk import SmsSDK from flask import current_app from application import redis @jsonrpc.method(name="Home.sms") def sms(mobile): """傳送簡訊驗證碼""" # 1.驗證手機號是否符合規則 if not re.match("^1[3-9]\d{9}$",mobile): return {"errno": status.CODE_VALIDATE_ERROR, "errmsg": message.mobile_format_error} # 2.簡訊傳送冷卻時間 ret = redis.get("int_%s" % mobile) if ret is not None: return {"errno": status.CODE_INTERVAL_TIME, "errmsg": message.sms_interval_time} # 3.通過隨機數生成驗證碼 sms_code = "%06d" % random.randint(0,999999) # 4.傳送簡訊 sdk = SmsSDK( current_app.config.get("SMS_ACCOUNT_ID"), current_app.config.get("SMS_ACCOUNT_TOKEN"), current_app.config.get("SMS_APP_ID") ) ret = sdk.sendMessage( current_app.config.get("SMS_TEMPLATE_ID"), mobile, (sms_code, current_app.config.get("SMS_EXPIRE_TIME") // 60) ) result = json.loads(ret) if result["statusCode"] == "000000": pipe = redis.pipeline() pipe.multi() # 開啟事務 # 儲存簡訊記錄到redis中 pipe.setex("sms_%s" % mobile,current_app.config.get("SMS_EXPIRE_TIME"),sms_code) # 進行冷卻倒數計時 pipe.setex("int_%s" % mobile,current_app.config.get("SMS_INTERVAL_TIME"),"_") pipe.execute() # 提交事務 # 返回結果 return {"errno":status.CODE_OK, "errmsg": message.ok} else: return {"errno": status.CODE_SMS_ERROR, "errmsg": message.sms_send_error}
2.客戶端實現傳送簡訊
客戶端點選獲取驗證碼按鈕,向後端發起請求,讓後端訪問ronglianyun傳送簡訊驗證碼
<div class="form-item"> <label class="text">驗證碼</label> <input type="text" class="code" v-model="sms_code" placeholder="請輸入驗證碼"> <img class="refresh" @click="send" src="../static/images/refresh.png"> </div> <script> apiready = function(){ init(); new Vue({ methods:{ send(){ // 點選傳送簡訊 if (!/1[3-9]\d{9}/.test(this.mobile)){ api.alert({ title: "警告", msg: "手機號碼格式不正確!", }); return; // 阻止程式碼繼續往下執行 } if(this.is_send){ api.alert({ title: "警告", msg: `簡訊傳送冷卻中,請${this.send_interval}秒之後重新點選傳送!`, }); return; // 阻止程式碼繼續往下執行 } this.axios.post("",{ "jsonrpc": "2.0", "id": this.uuid(), "method": "Home.sms", "params": { "mobile": this.mobile, } }).then(response=>{ if(response.data.result.errno != 1000){ api.alert({ title: "錯誤提示", msg: response.data.result.errmsg, }); }else{ this.is_send=true; // 進入冷卻狀態 this.send_interval = 60; var timer = setInterval(()=>{ this.send_interval--; if(this.send_interval<1){ clearInterval(timer); this.is_send=false; // 退出冷卻狀態 } }, 1000); } }).catch(error=>{ this.game.print(error.response.data.error); }); }, </script>