目錄
1.使用者註冊
1.後端完成對簡訊驗證碼的校驗
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 from application import redis class UserSchema(SQLAlchemyAutoSchema): ... @validates_schema def validate(self,data, **kwargs): .... '''校驗簡訊驗證碼''' # 1. 從redis中提取驗證碼 redis_sms_code = redis.get("sms_%s" % data["mobile"]) if redis_sms_code is None: raise ValidationError(message=Message.sms_code_expired,field_name="sms_code") redis_sms_code = redis_sms_code.decode() #2. 從客戶端提交的資料data中提取驗證碼 sms_code = data["sms_code"] #3. 字串比較,如果失敗,則丟擲異常,否則,直接刪除驗證碼 if sms_code != redis_sms_code: raise ValidationError(message=Message.sms_code_error, field_name="sms_code") redis.delete("sms_%s" % data["mobile"]) return data
2.基於celery實現簡訊非同步傳送
1.安裝celery
pip install celery==4.4.0
2.celery主程式檔案:main.py
在專案根目錄下建立mycelery目錄,同時建立celery啟動主程式檔案main.py,程式碼:
from __future__ import absolute_import from celery import Celery from application import init_app # 初始化celery物件 app = Celery("flask") # 初始化flask flask_app = init_app("application.settings.dev").app # 載入配置 app.config_from_object("mycelery.config") # 自動註冊任務 app.autodiscover_tasks(["mycelery.sms"])
3.celery配置檔案:config.py
# 任務佇列地址 broker_url = 'redis://127.0.0.1:6379/15' # 結果佇列地址 result_backend = "redis://127.0.0.1:6379/14"
4.建立任務模組:sms/tasks.py
同時,在任務執行過程中, 基於監聽器和任務bind屬性對失敗任務進行記錄和重新嘗試執行. 程式碼:
import json from application import redis from flask import current_app from ronglian_sms_sdk import SmsSDK from mycelery.main import app,flask_app @app.task(name="send_sms",bind=True) def send_sms(self,mobile,sms_code): """傳送簡訊""" try: with flask_app.app_context(): 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() # 提交事務 else: current_app.log.error("簡訊傳送失敗!\r\n%s" % ret) raise Exception except Exception as exc: # 重新嘗試執行失敗任務 print(self.request.retries) # 本次執行的次數 self.retry(exc=exc, countdown=3, max_retries=5) """基於監聽器完成任務監聽""" from celery.app.task import Task class SMSTask(Task): def on_success(self, retval, task_id, args, kwargs): print( '任務執行成功!') return super().on_success(retval, task_id, args, kwargs) def on_failure(self, exc, task_id, args, kwargs, einfo): print('任務執行失敗!%s' % self.request.retries) # 重新嘗試執行失敗任務,時間間隔:3秒,最大嘗試次數:5次 self.retry(exc=exc, countdown=3, max_retries=5) return super().on_failure(exc, task_id, args, kwargs, einfo) def after_return(self, status, retval, task_id, args, kwargs, einfo): print('this is after return') return super().after_return(status, retval, task_id, args, kwargs, einfo) def on_retry(self, exc, task_id, args, kwargs, einfo): print('this is retry') return super().on_retry(exc, task_id, args, kwargs, einfo)
5.flask專案呼叫非同步任務傳送簡訊
@jsonrpc.method(name="Home.sms") def sms(mobile): """傳送簡訊驗證碼""" # 驗證手機 if not re.match("^1[3-9]\d{9}$",mobile): return {"errno": status.CODE_VALIDATE_ERROR, "errmsg": message.mobile_format_error} # 簡訊傳送冷卻時間 ret = redis.get("int_%s" % mobile) if ret is not None: return {"errno": status.CODE_INTERVAL_TIME, "errmsg": message.sms_interval_time} # 生成驗證碼 sms_code = "%06d" % random.randint(0,999999) try: # 非同步傳送簡訊 ****** from mycelery.sms.tasks import send_sms send_sms.delay(mobile=mobile, sms_code=sms_code) # 返回結果 return {"errno":status.CODE_OK, "errmsg": message.sms_is_send} except Exception as e: return {"errno": status.CODE_SMS_ERROR, "errmsg": message.sms_send_error}
6.執行celery
在第一個終端執行celery
主程式終端下啟動: celery -A mycelery.main worker -l info
排程器終端下啟動: celery -A mycelery.main beat
再開一個終端,輸入如下指令
python manage.py shell >>> from mycelery.sms.tasks import send_sms >>> send_sms.delay(mobile="13928836666",sms_code="123456")
2.使用者登入
1.jwt登入驗證:flask_jwt_extended
1.flask_jwt_extended簡介
在flask中,我們可以通過flask_jwt_extended
模組來快速實現jwt使用者登入認證。
flask_jwt_extended
的作者開發當前模組主要適用於flask的普通檢視方法的。其認證方式主要通過裝飾器來完成。而我們當前所有服務端介面都改造成了jsonrpc規範介面,所以我們在使用過程中,需要對部分原始碼進行調整才能正常使用。事實上,在我們當前使用的
flask_jsonrpc
也提供了使用者登陸認證功能,但是這個功能是依靠使用者賬戶username
和密碼password
來實現。如果我們基於當前這種方式,也可以實現jwt登陸認證,只是相對於上面的flask_jwt_extended
模組而言,要補充的程式碼會更多,所以在此,我們放棄這塊功能的使用。
2.模組安裝
pip install flask-jwt-extended
配置說明:
3.初始化
import os,sys # 引入flask_jwt_extended模組 from flask_jwt_extended import JWTManager # jwt認證模組例項化 jwt = JWTManager() def init_app(config_path): """全域性初始化""" # jwt初始化 jwt.init_app(app) return manager
4.jwt相關配置
from . import InitConfig class Config(InitConfig): """專案開發環境下的配置""" ...... # jwt 相關配置 # 加密演算法,預設: HS256 JWT_ALGORITHM = "HS256" # 祕鑰,預設是flask配置中的SECRET_KEY JWT_SECRET_KEY = "y58Rsqzmts6VCBRHes1Sf2DHdGJaGqPMi6GYpBS4CKyCdi42KLSs9TQVTauZMLMw" # token令牌有效期,單位: 秒/s,預設: datetime.timedelta(minutes=15) 或者 15 * 60 JWT_ACCESS_TOKEN_EXPIRES = 60 # refresh重新整理令牌有效期,單位: 秒/s,預設:datetime.timedelta(days=30) 或者 30*24*60*60 JWT_REFRESH_TOKEN_EXPIRES = 30*24*60*60 # 設定通過哪種方式傳遞jwt,預設是http請求頭,也可以是query_string,json,cookies JWT_TOKEN_LOCATION = "headers" # 當通過http請求頭傳遞jwt時,請求頭引數名稱設定,預設值: Authorization JWT_HEADER_NAME="Authorization" # 當通過http請求頭傳遞jwt時,令牌的字首。 # 預設值為 "Bearer",例如:Authorization: Bearer <JWT> JWT_HEADER_TYPE="jwt"
from application import jsonrpc,db from .marshmallow import MobileSchema,UserSchema from marshmallow import ValidationError from message import ErrorMessage as Message from status import APIStatus as status ...... from flask_jwt_extended import create_access_token,create_refresh_token,jwt_required,get_jwt_identity,jwt_refresh_token_required from flask import jsonify,json @jsonrpc.method("User.login") def login(account,password): """根據使用者登入資訊生成token""" # 1. todo 根據賬戶資訊和密碼獲取使用者 # 2. 生成jwt token access_token = create_access_token(identity=account) refresh_token = create_refresh_token(identity=account) return "ok" @jsonrpc.method("User.info") @jwt_required # 驗證jwt def info(): """獲取使用者資訊""" user_data = json.loads(get_jwt_identity()) # get_jwt_identity 用於獲取載荷中的資料 return "ok" @jsonrpc.method("User.refresh") @jwt_refresh_token_required def refresh(): """重新獲取新的認證令牌token""" current_user = get_jwt_identity() # 重新生成token access_token = create_access_token(identity=current_user) return access_token
6.修改jwt原始碼
flask_jwt_extended/view_decorators.py
,程式碼:
from jwt.exceptions import ExpiredSignatureError from flask_jwt_extended.exceptions import InvalidHeaderError from message import ErrorMessage as message from status import APIStatus as status def jwt_required(fn):
@wraps(fn) def wrapper(*args, **kwargs): try: verify_jwt_in_request() except NoAuthorizationError: return {"errno":status.CODE_NO_AUTHORIZATION,"errmsg":message.no_authorization} except ExpiredSignatureError: return {"errno":status.CODE_SIGNATURE_EXPIRED,"errmsg":message.authorization_has_expired} except InvalidHeaderError: return {"errno":status.CODE_INVALID_AUTHORIZATION,"errmsg":message.authorization_is_invalid} return fn(*args, **kwargs) return wrapper
def jwt_refresh_token_required(fn): @wraps(fn) def wrapper(*args, **kwargs): try: verify_jwt_refresh_token_in_request() except NoAuthorizationError: return {"errno":status.CODE_NO_AUTHORIZATION,"errmsg":message.no_authorization} except ExpiredSignatureError: return {"errno":status.CODE_SIGNATURE_EXPIRED,"errmsg":message.authorization_has_expired} except InvalidHeaderError: return {"errno":status.CODE_INVALID_AUTHORIZATION,"errmsg":message.authorization_is_invalid} return fn(*args, **kwargs) return wrapper
2.服務端提供使用者登入的API介面
from flask_jwt_extended import create_access_token,create_refresh_token,jwt_required,get_jwt_identity,jwt_refresh_token_required from flask import jsonify,json from sqlalchemy import or_ from .models import User from message import ErrorMessage as message from status import APIStatus as status @jsonrpc.method("User.login") def login(account,password): """根據使用者登入資訊生成token""" # 1. 根據賬戶資訊和密碼獲取使用者 if len(account) < 1: return {"errno":status.CODE_NO_ACCOUNT,"errmsg":message.account_no_data} user = User.query.filter(or_( User.mobile==account, User.email==account, User.name==account )).first() # 檢測使用者是否存在 if user is None: return {"errno": status.CODE_NO_USER,"errmsg":message.user_not_exists} # 驗證密碼 if not user.check_password(password): return {"errno": status.CODE_PASSWORD_ERROR, "errmsg":message.password_error} # 2. 生成jwt token access_token = create_access_token(identity=user.id) refresh_token = create_refresh_token(identity=user.id) return {"access_token": access_token,"refresh_token":refresh_token} @jsonrpc.method("User.info") @jwt_required # 驗證jwt def info(): """獲取使用者資訊""" user_data = json.loads(get_jwt_identity()) # get_jwt_identity 用於獲取載荷中的資料 print(user_data) return "ok" @jsonrpc.method("User.refresh") @jwt_refresh_token_required def refresh(): """重新獲取新的認證令牌token""" current_user = get_jwt_identity() # 重新生成token access_token = create_access_token(identity=current_user) return access_token