数据工厂系列(6)编写登录接口

255 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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的验证和进行权限校验~

好了,今天先到这里了,我们下期见哈~