持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
大家好~我是小方,欢迎大家关注笋货测试笔记体完记得俾个like呀
上篇回顾
之前我们已经优化好了了代码,现在我们可以好好建设高楼大厦了~今天要实现的功能是用户登录!!!
登录接口编写
- 入参模型和返参模型定义
入参模型
class LoginUserBody(BaseModel):
username: str = Field(..., title="用户名", description="必传")
password: str = Field(..., title="密码", description="必传")
@validator('username', 'password')
def check_field(cls, v):
return ToolsSchemas.not_empty(v)
@validator('password')
def md5_paw(cls, value):
m = hashlib.md5()
m.update(f"{value}key={Config.KEY}".encode("utf-8"))
return m.hexdigest()
返参模型
class UserDto(BaseModel):
username: str
name: str
email: str
role: int
is_valid: bool
last_login_time: datetime
class Config:
orm_mode = True
json_encoders = {
datetime: lambda v: v.strftime("%Y-%m-%d %H:%M:%S")
}
class UserTokenDto(UserDto):
token: str
class LoginResDto(ResponseDto):
msg: str = '登录成功'
data: UserTokenDto
orm_mode = True 这里意思是将表对象映射到pydantic的模型,json_encoders可以自定义json编码规则,上面的栗子就是将datetime类型的时间进行格式化
- UserDao逻辑编写
@classmethod
@record_log
def user_login(cls, data: LoginUserBody) -> DataFactoryUser:
"""
:param data: 用户模型
:return:
"""
with Session() as session:
user = session.query(DataFactoryUser).filter(DataFactoryUser.username == data.username, DataFactoryUser.password == data.password).first()
if user is None:
raise Exception("用户名或密码错误")
if user.is_valid:
# is_valid == true, 说明被冻结了
raise Exception("对不起, 你的账号已被冻结, 请联系管理员处理")
user.last_login_time = datetime.now()
session.commit()
# 进行对象刷新,更新对象,让对象过期,从而在下次访问时重新加载
session.refresh(user)
return user
逻辑比较简单,比较重点的是refresh这个方法,上面我们对user.last_login_time进行更新,由于缓存机制,在数据过期前,都是直接读取缓存,如果这时候你返回user,user还是以前的状态,所以进行对象刷新,更新对象,让对象过期,从而在下次访问时重新加载
路由函数编写
@router.post('/login', name='用户登录', description='用户登录', response_model=LoginResDto)
def login(data: LoginUserBody):
try:
user = UserDao.user_login(data)
# 先来mock 假的token信息
setattr(user, 'token', 'rng niubi~')
return LoginResDto(data=user)
except Exception as e:
raise NormalException(str(e))
用户工具类编写
上面的token只是我们mock的一个数据,准确来说,token应该是一个加密字符串,token解密后,可以得出用户的信息,这里token的生成,我采用的是JWT实现方式,全称是JSON Web Token
- JWT组成
- 标头(Header)
- 有效载荷(Payload)
- 签名(Signature)
在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串,更详细JWT的了解,大家可自行百度搜索相关的资料哈~
接下来,我们来编写生成token和解析token的工具类~
import jwt
from jwt.exceptions import ExpiredSignatureError
from datetime import timedelta, datetime
from config import Config
class UserToken(object):
@staticmethod
def get_token(data: dict) -> str:
"""
:param data: 用户数据
:return:
"""
# 默认加密方式为 HS256, 过期时间 = 现在时间 + 配置过期时长
token_data = dict({"exp": datetime.utcnow() + timedelta(hours=Config.EXPIRED_HOUR)}, **data)
return jwt.encode(token_data, key=Config.KEY)
@staticmethod
def parse_token(token: str) -> dict:
"""解析token"""
try:
return jwt.decode(token, key=Config.KEY, algorithms=["HS256"])
# token 过期
except ExpiredSignatureError:
raise Exception("token已过期, 请重新登录")
# 解析失败
except Exception:
raise Exception("token解析失败, 请重新登录")
config.py记得加上
路由函数相应改造
@router.post('/login', name='用户登录', description='用户登录', response_model=LoginResDto)
def login(data: LoginUserBody):
try:
user = UserDao.user_login(data)
# 将类加载数据到模型中
user_model = UserDto.from_orm(user)
# xx.dict() 返回模型的字段和值的字典
# 返回表示 dict() 的 JSON 字符串,只有当转换为json,模型里面的编码规则(json_encoders)才生效
user_data = user_model.json()
token = UserToken.get_token(json.loads(user_data))
setattr(user, 'token', token)
return LoginResDto(data=user)
except Exception as e:
raise NormalException(str(e))
最终测试一下
账号被冻结
总结
今天编写了登录接口,简单介绍了JWT,我们现在节奏有点慢,之后的教程可能会比较快的哦,大家记得扣好安全带!下一期我们讲一下如何使用Depends实现token的验证和进行权限校验~
好了,今天先到这里了,我们下期见哈~
- 项目地址