添加手机验证码

This commit is contained in:
Administrator 2023-03-17 16:29:13 +08:00
parent e68e0776bc
commit 738768d412
6 changed files with 160 additions and 3 deletions

View File

@ -15,6 +15,11 @@ def get_user_info(db: Session, email: str):
return user return user
def get_user_info_by_phone(db: Session, phone: str):
user = db.query(User).filter_by(phone=phone).first()
return user
def get_full_user_info(db: Session, email: str): def get_full_user_info(db: Session, email: str):
user = db.query(User).filter_by(email=email).first() user = db.query(User).filter_by(email=email).first()
if not user: if not user:

View File

@ -8,6 +8,7 @@ class User(Base):
__tablename__ = "user" __tablename__ = "user"
# openid = Column(String(255), primary_key=True, comment="用户OpenID") # openid = Column(String(255), primary_key=True, comment="用户OpenID")
email = Column(String(255), primary_key=True, comment="邮箱") email = Column(String(255), primary_key=True, comment="邮箱")
phone = Column(String(255), comment="手机号")
name = Column(String(32), comment="用户名") name = Column(String(32), comment="用户名")
department = Column(Text, comment="部门") department = Column(Text, comment="部门")
post = Column(Text, comment="职务") post = Column(Text, comment="职务")
@ -24,6 +25,7 @@ class User(Base):
class UserInfo(Base): class UserInfo(Base):
__tablename__ = "user_info" __tablename__ = "user_info"
email = Column(String(255), primary_key=True, index=True, comment="邮箱") email = Column(String(255), primary_key=True, index=True, comment="邮箱")
phone = Column(String(255), comment="手机号")
name = Column(String(32), comment="用户名") name = Column(String(32), comment="用户名")
department = Column(Text, comment="部门") department = Column(Text, comment="部门")
post = Column(Text, comment="职务") post = Column(Text, comment="职务")

View File

@ -8,7 +8,7 @@ from Schemas.UserSchemas import TokenData
from Utils.AuthUtils import token_data_depend, create_token, registered_depend from Utils.AuthUtils import token_data_depend, create_token, registered_depend
from Utils.CrudUtils import auto_create_crud from Utils.CrudUtils import auto_create_crud
from Utils.SqlAlchemyUtils import get_db from Utils.SqlAlchemyUtils import get_db
from Utils.VerifyCodeUtils import EmailVerifyCode, EmailVerifyType from Utils.VerifyCodeUtils import EmailVerifyCode, EmailVerifyType, PhoneVerifyCode, PhoneVerifyType
from Utils.wxAppUtils import code2Session from Utils.wxAppUtils import code2Session
router = APIRouter( router = APIRouter(
@ -75,7 +75,7 @@ def change_user_info(req: UserSchemas.ChangeUserInfoReq, token_data: TokenData =
@router.post('/bind_email', tags=["用户接口"], summary='邮箱登录') @router.post('/bind_email', tags=["用户接口"], summary='邮箱登录')
def bind_email(req: UserSchemas.BindEmailReq, def bind_email(req: UserSchemas.BindEmailReq,
db: Session = Depends(get_db)): db: Session = Depends(get_db)):
email= req.email.replace(" ","") email = req.email.replace(" ", "")
checked = EmailVerifyCode.check_code(email, req.email_code, EmailVerifyType.change) checked = EmailVerifyCode.check_code(email, req.email_code, EmailVerifyType.change)
if not checked: if not checked:
raise HTTPException(detail="邮箱验证码错误", status_code=303) raise HTTPException(detail="邮箱验证码错误", status_code=303)
@ -96,6 +96,38 @@ def bind_email(req: UserSchemas.BindEmailReq,
return {'msg': "成功", 'state': 1, 'data': {'token': token}} return {'msg': "成功", 'state': 1, 'data': {'token': token}}
@router.post("/get_phone_verify_code", tags=["用户接口"], summary="获取短信验证码")
def get_phone_verify_code(body: UserSchemas.GetPhoneVerifyCodeReq, db=Depends(get_db)):
phone = body.phone
user = UserCrud.get_user_info_by_phone(db, phone)
if not user:
raise HTTPException(detail="手机号未录入系统", status_code=303)
try:
PhoneVerifyCode.send_code(phone, PhoneVerifyType.login)
except Exception as e:
print(e)
raise HTTPException(detail="验证码发送失败", status_code=403)
return {"msg": "验证码已发送至手机,请查看", "state": 1}
@router.post('/login_by_phone', tags=["用户接口"], summary='手机号登录')
def login_by_phone(req: UserSchemas.LoginByPhoneReq,
db: Session = Depends(get_db)):
phone = req.phone.replace(" ", "")
checked = PhoneVerifyCode.check_code(phone, req.code, PhoneVerifyType.login)
if not checked:
raise HTTPException(detail="验证码错误", status_code=303)
user = UserCrud.get_user_info_by_phone(db, phone)
if not user:
raise HTTPException(detail="手机号未录入系统", status_code=303)
auth_data = AuthCrud.get_user_auth(db, user.email)
user_data = user.to_dict()
user_data["auth_data"] = auth_data
token_data = TokenData(**user_data).dict()
token = create_token(token_data)
return {'msg': "成功", 'state': 1, 'data': {'token': token}}
@router.post("/get_email_verify_code", tags=["用户接口"], summary="获取邮箱验证码") @router.post("/get_email_verify_code", tags=["用户接口"], summary="获取邮箱验证码")
def get_email_verify_code(body: UserSchemas.EmailSendReqBody): def get_email_verify_code(body: UserSchemas.EmailSendReqBody):
print(body) print(body)

View File

@ -39,7 +39,7 @@ class GetUserInfoRes(BaseModel):
department: Optional[str] department: Optional[str]
department_list: Optional[List[DepartmentInfo]] department_list: Optional[List[DepartmentInfo]]
registered: Optional[bool] registered: Optional[bool]
auth_data:Optional[str] auth_data: Optional[str]
class TokenData(BaseModel): class TokenData(BaseModel):
@ -79,6 +79,15 @@ class EmailSendReqBody(BaseModel):
email: str = "xxxx@fecr.com.cn" email: str = "xxxx@fecr.com.cn"
class LoginByPhoneReq(BaseModel):
phone: str
code: str
class GetPhoneVerifyCodeReq(BaseModel):
phone: str
@unique @unique
class DepartmentTypeEnum(Enum): class DepartmentTypeEnum(Enum):
enum01 = "董监高" enum01 = "董监高"

68
Utils/PhoneMsgUtils.py Normal file
View File

@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
import time
import uuid
import hashlib
import base64
import requests
# 必填,请参考"开发准备"获取如下数据,替换为实际值
url = 'https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1' # APP接入地址(在控制台"应用管理"页面获取)+接口访问URI
APP_KEY = "3h499M186sTF8046f9J9I28J1L9m" # APP_Key
APP_SECRET = "TMCaZWxoMbuW55l27zdxoDfsiKAH" # APP_Secret
sender = "1069368924410005073" # 国内短信签名通道号或国际/港澳台短信通道号
TEMPLATE_ID = "527eb7a7b95f4466834347adbde6d53e" # 模板ID
# 条件必填,国内短信关注,当templateId指定的模板类型为通用模板时生效且必填,必须是已审核通过的,与模板类型一致的签名名称
# 国际/港澳台短信不用关注该参数
signature = "华为云短信测试" # 签名名称
# 必填,全局号码格式(包含国家码),示例:+86151****6789,多个号码之间用英文逗号分隔
receiver = "+86151****6789,+86152****7890" # 短信接收人号码
# 选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
statusCallBack = ""
'''
选填,使用无变量模板时请赋空值 TEMPLATE_PARAM = '';
单变量模板示例:模板内容为"您的验证码是${1}",TEMPLATE_PARAM可填写为'["369751"]'
双变量模板示例:模板内容为"您有${1}件快递请到${2}领取",TEMPLATE_PARAM可填写为'["3","人民公园正门"]'
模板中的每个变量都必须赋值且取值不能为空
查看更多模板和变量规范:产品介绍>模板和变量规范
'''
TEMPLATE_PARAM = '["369751"]' # 模板变量此处以单变量验证码短信为例请客户自行生成6位验证码并定义为字符串类型以杜绝首位0丢失的问题例如002569变成了2569
'''
构造X-WSSE参数值
@param appKey: string
@param appSecret: string
@return: string
'''
def buildWSSEHeader(appKey, appSecret):
now = time.strftime('%Y-%m-%dT%H:%M:%SZ') # Created
nonce = str(uuid.uuid4()).replace('-', '') # Nonce
digest = hashlib.sha256((nonce + now + appSecret).encode()).hexdigest()
digestBase64 = base64.b64encode(digest.encode()).decode() # PasswordDigest
return 'UsernameToken Username="{}",PasswordDigest="{}",Nonce="{}",Created="{}"'.format(appKey, digestBase64, nonce,
now);
def send_phone_code(phone, code):
# 请求Headers
header = {'Authorization': 'WSSE realm="SDP",profile="UsernameToken",type="Appkey"',
'X-WSSE': buildWSSEHeader(APP_KEY, APP_SECRET)}
# 请求Body
form_data = {'from': sender,
'to': "+86" + phone,
'templateId': TEMPLATE_ID,
'templateParas': f'["{code}"]',
'statusCallback': statusCallBack,
'signature': signature # 使用国内短信通用模板时,必须填写签名名称
}
# 为防止因HTTPS证书认证失败造成API调用失败,需要先忽略证书信任问题
r = requests.post(url, data=form_data, headers=header, verify=False)
print(r.text)
# send_phone_code("+8618090478123","34567812")

View File

@ -2,6 +2,7 @@ import uuid
from enum import Enum from enum import Enum
from captcha.image import ImageCaptcha from captcha.image import ImageCaptcha
from Utils.EmailUtils import send_email from Utils.EmailUtils import send_email
from Utils.PhoneMsgUtils import send_phone_code
from Utils.RandomUtils import get_random_num_code, get_random_letter_and_num_code from Utils.RandomUtils import get_random_num_code, get_random_letter_and_num_code
from Utils.RedisUtils import redis_pool from Utils.RedisUtils import redis_pool
@ -13,6 +14,13 @@ class EmailVerifyType(Enum):
change = 4 change = 4
class PhoneVerifyType(Enum):
login = 1
register = 2
reset_password = 3
change = 4
class ImageCaptchaVerify: class ImageCaptchaVerify:
@classmethod @classmethod
def make_captcha_image(cls, code=None, expire_time_s: int = 120): def make_captcha_image(cls, code=None, expire_time_s: int = 120):
@ -33,6 +41,39 @@ class ImageCaptchaVerify:
return True return True
class PhoneVerifyCode:
@classmethod
def get_phone_id(cls, phone, verify_type: PhoneVerifyType = PhoneVerifyType.login):
"""
:param phone:
:param verify_type: 不同业务验证类型对应不同的id
:return:str
"""
return f"{phone}_EmailVerifyCodeId_{verify_type.name}"
@classmethod
def make_code(cls, phone, expire_time_s=60 * 5, verify_type: PhoneVerifyType = PhoneVerifyType.login):
client = redis_pool.get_redis_client()
email_id = cls.get_phone_id(phone, verify_type)
code = get_random_num_code(4)
client.set(email_id, code)
if expire_time_s:
client.expire(email_id, expire_time_s)
return code
@classmethod
def send_code(cls, phone, verify_type: PhoneVerifyType):
code = cls.make_code(phone, verify_type=verify_type)
send_phone_code(phone, code)
@classmethod
def check_code(cls, phone, code, verify_type: PhoneVerifyType = PhoneVerifyType.login):
phone_id = cls.get_phone_id(phone, verify_type=verify_type)
client = redis_pool.get_redis_client()
phone_code = client.get(phone_id)
return code and code == phone_code
class EmailVerifyCode: class EmailVerifyCode:
@classmethod @classmethod
def get_email_id(cls, email, verify_type: EmailVerifyType = EmailVerifyType.login): def get_email_id(cls, email, verify_type: EmailVerifyType = EmailVerifyType.login):