开发日志
模型校验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这是为什么