2.註冊功能基本實現git
1.關於手機號碼的惟一性驗證github
2.保存用戶註冊信息redis
3.發送短信驗證碼sql
數據庫
https://marshmallow-sqlalchemy.readthedocs.io/en/latest/json
注意:flask_marshmallow在0.12.0版本之後已經移除了ModelSchema和TableSchema這兩個模型構造器類,官方轉而推薦了使用SQLAlchemyAutoSchema和SQLAlchemySchema這2個類,先後二者用法相似。flask
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.甚至能夠聲明一些非模型內的字段:直接模型類對象.屬性便可
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中
在開發中,針對客戶端提交的數據進行驗證或提供模型數據轉換格式成字典給客戶端。可使用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 = "對不起,當前手機已經被註冊!"
爲了方便導包,因此咱們設置當前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")) ......
在客戶端輸入手機號,觸發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>
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 = "密碼和驗證密碼不匹配!"
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>
# 短信相關配置 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}
客戶端點擊獲取驗證碼按鈕,向後端發起請求,讓後端訪問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>