泉山石涧
首页
天气后端开发手册
中间键
CSS实例
用户鉴权JWT
其他笔记
首页
天气后端开发手册
中间键
CSS实例
用户鉴权JWT
其他笔记
  • 开发日志

开发日志

模型校验pydantic

field_validator方法

#使用该装饰器可以对定义的模型中的成员进行类型校验
#使用正则表达式可以先定用户可以输入的合法字符
@field_validator("password")
    def validate_password(cls, value: str) -> str:
        pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\S]{8,20}$"
        if not re.fullmatch(pattern, value):
            raise ValueError(
                "密码必须8~20字符,且包含至少一个大写字母、小写字母和数字"
            )
        return value
question

持久化的会话

@app.websocket
@router.add_api_websocket_route
#token的另一种解法?或者双层保险。登录后的某些会话可以使用
路径路由
资源使用
权限与JWT
websocket
后面的许多可等到完成全部以后进行复盘

周四周五计划:

1.看jwt相关课程与实践
2.tokens
3.websocket学习与实践
4.管理员用户权限

路径与路由

路径与简易短连接

独立变量
预设值变量
包含路径的变量
短连接服务

路径变量是path,路径查询参数是Query( ),二者时间的区别是?

(*后面的参数是关键字),传参必须以关键字的形式进行

查询参数可以使用别名(alias):

​ *为了更方便解释(数据库中的offset别名为page)

​ *为了方便转化(如list可以转为使用数组)

上传文件

@app.post("upload_file/{path_var}",summary="上传文件")
async def upload_file(*,
                      file: UploadFile,#请求体,声明数据模型的类
                      path_var: str | None = None,#路径变量
					  code: str| None = Query(None,min_length=4,max_length=8,alais="token")#路径参数
):
    file_local = await save_files(file)
    return{
        "file_name":file.filename,
        "content_type":file.content_type,
        "path_var":path_var,
        "code":code,
        "file_local":file_local
    }

async def save_files(file):
    path = "files"
    res = await file.read()
    hash_name = hashlib.md5(file.filename.encode()).hexdigest()[:8]
    file_name = f"{fashed_name}.{file.filename.rsplit('.',1)[-1]}"
    full_file = f"{path}/{file_name}"
    with open(full_file,"wb") as f:
        f.write(res)
    return full_file

用户鉴权

依赖项

依赖项的调用是从底层往上层依次调用的;

依赖项有缓存,被调用过一次的依赖项会将结果缓存,它只被调用一次

函数与类都可以作为依赖项

依赖项可以在路径装饰器与路径操作函数中使用,但是路径装饰器中使用时无法返回结果

Security依赖项可以实现控制执行操作的人的角色(权限)

#RBAC,基于角色的权限控制
ALL_USERS={
    'jack':['admin','user'],
    'rose':['admin','user'],
    'tom':['user'],
    'jerrt':['user']
}

ROLE_PERMISSIONS = {
    'admin':['upload'],
    'users':['visit','download'],
}

#模拟登录,获取Cookie
@router.get("/login")
async def set_cookie(resp:Response,token: str):
    resp.set_cookie(key="user_name",value=token,expires=600)
    return {"message":"cookie set"}

#获取登录的用户名
def get_user_token(user_name: str| None=Cookie(default=None)):
    if user_name is None:
        raise HTTPException(status_code=status.HTTP_401,detail="需要用户名")
        return user_name

#获取他的角色所拥有的所有操作权限
def get_role_permissions(role_name:list[str]):
    permissions = []
    for role in role_name:
        for perm in ROLE_PERMISSIONS[role]:
            permissions.append(perm)
    return permissions

#获取登录的用户[token](如果该用户存在)的所有角色
def get_user_permissions(token: str = Depends(get_user_token)):
    if token in ALL_USERS:
        return get_role_permissions(ALL_USERS[token])
    return None

#检测用户是否拥有执行该操作的权限
def check_user(security_scopes:SecurityScopes,user_permission:str=Depends(get_user_permissions)):
    for scope in security_scopes.scopes:
        if scope not in user_permission:
            raise HTTPException(status_code=status.HTTP_401,detail="你没有权限!")
   
#将check_user嵌入具体的操作路由中
@router.post("/test",dependencies=[Secutiry(check_user,scopes=['upload'])])
''''''

JWT

十二要素

https://12factor.net/zh_cn/

schemas

https://blog.csdn.net/inthat/article/details/131314014


当前任务与计划:

api请求的基本功能已经实现。

Next step:

学习鉴权机制的实现(看课程);

将当前的查询demo中加入鉴权的功能;

将demo完善(请求api是否使用轮询?)

​ -查询

  • 城市数据入库与更新

  • 短信发送功能

  • 查询可以将用户请求的数据记录入redis,在一定的时间内可以减少api的使用量

什么是requests类?

什么是子程序挂载?-创建线程

什么是返回类的工作函数?

SecurityScopes?

登录中生成token

@router.post("/login/by_pswd")
async def login_by_pswd(
     user:schemas.UserLoginByPassword,
     response: Response,
     db:AsyncSession = Depends(databases.get_async_db),
     redis_client: Redis = Depends(redis_connect)
):
    try:
        
        login_user=await crud.get_user_by_email(db,user.email)
        if not login_user:
            raise HTTPException(401,detail="Email do not registered")
        else:
            ret = services.verify_password(user.password,login_user.password)
            if ret == True:
                token = await services.create_session_token(login_user.user_id, redis_client)
                '''response.set_cookie(
                    key="session_token", 
                    value=token, 
                    httponly=True, 
                    secure=True,  # 如果是https环境下建议开启
                    max_age=3600  # 过期时间(秒)
                )'''
                return {"message":"login sucsessfully !"}
            else:
                # 密码错误的情况
                raise HTTPException(status_code=401, detail="password error !")
    except HTTPException as http_exc:
        raise http_exc
    except Exception as e:
        print(f"登录过程中发生未知错误: {e}") 
        raise HTTPException( status_code=500, detail="服务器内部错误,请稍后重试")

@router.post("/login/by_verification_code")
async def login_by_Vcode(
    user:schemas.UserEmail,
    db:AsyncSession = Depends(databases.get_async_db),
    redis_client: Redis = Depends(redis_connect)
):
    try:
        login_user=await crud.get_user_by_email(db,user.email)
        if not login_user:
            raise HTTPException(401,detail="Email do not registered")
        else:
            if not await services.check_email_rate_limit(user.email,redis_client): 
                raise HTTPException (409,detail="Verification send too fast !")
            await redis_client.rpush("email_queue", json.dumps({
                "email": user.email,
                "timestamp": time.time()
                }))        
    except HTTPException as http_exc:
        raise http_exc
    except Exception as e:
        print(f"登录过程中发生未知错误: {e}") 
        raise HTTPException( status_code=500, detail="服务器内部错误,请稍后重试")
     127.0.0.1:50095 - "POST /api/users/login HTTP/1.1" 307 Temporary Redirect
INFO:     127.0.0.1:50095 - "POST /api/users/login/by_pswd HTTP/1.1" 422 Unprocessable Entity这是为什么