mirror of
https://github.com/fastapi-practices/fastapi_best_architecture.git
synced 2025-08-26 04:33:09 +08:00

* [WIP] Add OAuth 2.0 authorization login * Add social user relationship table * Update social user relationship table back_populates * Add OAuth 2.0 related interface * Automatically redirect authorization addresses * Update OAuth2 authorization to GitHub * Add implementation code * fix the callback interface return * fix typo * fix the api return * fix imports * Fix logic for creating system users and social tables * Fix user information storage * Add OAuth2 source link * remove unnecessary db refresh * remove the front end docker-compose annotation
229 lines
10 KiB
Python
229 lines
10 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
import random
|
||
|
||
from fastapi import Request
|
||
from sqlalchemy import Select
|
||
|
||
from backend.app.common.exception import errors
|
||
from backend.app.common.jwt import get_token, password_verify, superuser_verify
|
||
from backend.app.common.redis import redis_client
|
||
from backend.app.core.conf import settings
|
||
from backend.app.crud.crud_dept import dept_dao
|
||
from backend.app.crud.crud_role import role_dao
|
||
from backend.app.crud.crud_user import user_dao
|
||
from backend.app.database.db_mysql import async_db_session
|
||
from backend.app.models import User
|
||
from backend.app.schemas.user import (
|
||
AddUserParam,
|
||
AvatarParam,
|
||
RegisterUserParam,
|
||
ResetPasswordParam,
|
||
UpdateUserParam,
|
||
UpdateUserRoleParam,
|
||
)
|
||
|
||
|
||
class UserService:
|
||
@staticmethod
|
||
async def register(*, obj: RegisterUserParam) -> None:
|
||
async with async_db_session.begin() as db:
|
||
if not obj.password:
|
||
raise errors.ForbiddenError(msg='密码为空')
|
||
username = await user_dao.get_by_username(db, obj.username)
|
||
if username:
|
||
raise errors.ForbiddenError(msg='该用户名已注册')
|
||
obj.nickname = obj.nickname if obj.nickname else f'用户{random.randrange(10000, 99999)}'
|
||
nickname = await user_dao.get_by_nickname(db, obj.nickname)
|
||
if nickname:
|
||
raise errors.ForbiddenError(msg='昵称已注册')
|
||
email = await user_dao.check_email(db, obj.email)
|
||
if email:
|
||
raise errors.ForbiddenError(msg='该邮箱已注册')
|
||
await user_dao.create(db, obj)
|
||
|
||
@staticmethod
|
||
async def add(*, request: Request, obj: AddUserParam) -> None:
|
||
async with async_db_session.begin() as db:
|
||
await superuser_verify(request)
|
||
username = await user_dao.get_by_username(db, obj.username)
|
||
if username:
|
||
raise errors.ForbiddenError(msg='此用户名已注册')
|
||
obj.nickname = obj.nickname if obj.nickname else f'用户{random.randrange(10000, 99999)}'
|
||
nickname = await user_dao.get_by_nickname(db, obj.nickname)
|
||
if nickname:
|
||
raise errors.ForbiddenError(msg='昵称已注册')
|
||
if not obj.password:
|
||
raise errors.ForbiddenError(msg='密码为空')
|
||
email = await user_dao.check_email(db, obj.email)
|
||
if email:
|
||
raise errors.ForbiddenError(msg='该邮箱已注册')
|
||
dept = await dept_dao.get(db, obj.dept_id)
|
||
if not dept:
|
||
raise errors.NotFoundError(msg='部门不存在')
|
||
for role_id in obj.roles:
|
||
role = await role_dao.get(db, role_id)
|
||
if not role:
|
||
raise errors.NotFoundError(msg='角色不存在')
|
||
await user_dao.add(db, obj)
|
||
|
||
@staticmethod
|
||
async def pwd_reset(*, request: Request, obj: ResetPasswordParam) -> int:
|
||
async with async_db_session.begin() as db:
|
||
if not await password_verify(f'{obj.old_password}{request.user.salt}', request.user.password):
|
||
raise errors.ForbiddenError(msg='旧密码错误')
|
||
np1 = obj.new_password
|
||
np2 = obj.confirm_password
|
||
if np1 != np2:
|
||
raise errors.ForbiddenError(msg='两次密码输入不一致')
|
||
count = await user_dao.reset_password(db, request.user.id, obj.new_password, request.user.salt)
|
||
prefix = [
|
||
f'{settings.TOKEN_REDIS_PREFIX}:{request.user.id}:',
|
||
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{request.user.id}:',
|
||
]
|
||
for i in prefix:
|
||
await redis_client.delete_prefix(i)
|
||
return count
|
||
|
||
@staticmethod
|
||
async def get_userinfo(*, username: str) -> User:
|
||
async with async_db_session() as db:
|
||
user = await user_dao.get_with_relation(db, username=username)
|
||
if not user:
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
return user
|
||
|
||
@staticmethod
|
||
async def update(*, request: Request, username: str, obj: UpdateUserParam) -> int:
|
||
async with async_db_session.begin() as db:
|
||
if not request.user.is_superuser:
|
||
if request.user.username != username:
|
||
raise errors.ForbiddenError(msg='你只能修改自己的信息')
|
||
input_user = await user_dao.get_with_relation(db, username=username)
|
||
if not input_user:
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
if input_user.username != obj.username:
|
||
_username = await user_dao.get_by_username(db, obj.username)
|
||
if _username:
|
||
raise errors.ForbiddenError(msg='该用户名已存在')
|
||
if input_user.nickname != obj.nickname:
|
||
nickname = await user_dao.get_by_nickname(db, obj.nickname)
|
||
if nickname:
|
||
raise errors.ForbiddenError(msg='改昵称已存在')
|
||
if input_user.email != obj.email:
|
||
email = await user_dao.check_email(db, obj.email)
|
||
if email:
|
||
raise errors.ForbiddenError(msg='该邮箱已注册')
|
||
count = await user_dao.update_userinfo(db, input_user, obj)
|
||
return count
|
||
|
||
@staticmethod
|
||
async def update_roles(*, request: Request, username: str, obj: UpdateUserRoleParam) -> None:
|
||
async with async_db_session.begin() as db:
|
||
if not request.user.is_superuser:
|
||
if request.user.username != username:
|
||
raise errors.ForbiddenError(msg='你只能修改自己的角色')
|
||
input_user = await user_dao.get_with_relation(db, username=username)
|
||
if not input_user:
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
for role_id in obj.roles:
|
||
role = await role_dao.get(db, role_id)
|
||
if not role:
|
||
raise errors.NotFoundError(msg='角色不存在')
|
||
await user_dao.update_role(db, input_user, obj)
|
||
await redis_client.delete_prefix(f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.uuid}')
|
||
|
||
@staticmethod
|
||
async def update_avatar(*, request: Request, username: str, avatar: AvatarParam) -> int:
|
||
async with async_db_session.begin() as db:
|
||
if not request.user.is_superuser:
|
||
if request.user.username != username:
|
||
raise errors.ForbiddenError(msg='你只能修改自己的头像')
|
||
input_user = await user_dao.get_by_username(db, username)
|
||
if not input_user:
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
count = await user_dao.update_avatar(db, input_user, avatar)
|
||
return count
|
||
|
||
@staticmethod
|
||
async def get_select(*, dept: int, username: str = None, phone: str = None, status: int = None) -> Select:
|
||
return await user_dao.get_all(dept=dept, username=username, phone=phone, status=status)
|
||
|
||
@staticmethod
|
||
async def update_permission(*, request: Request, pk: int) -> int:
|
||
async with async_db_session.begin() as db:
|
||
await superuser_verify(request)
|
||
if not await user_dao.get(db, pk):
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
else:
|
||
if pk == request.user.id:
|
||
raise errors.ForbiddenError(msg='禁止修改自身管理员权限')
|
||
count = await user_dao.set_super(db, pk)
|
||
return count
|
||
|
||
@staticmethod
|
||
async def update_staff(*, request: Request, pk: int) -> int:
|
||
async with async_db_session.begin() as db:
|
||
await superuser_verify(request)
|
||
if not await user_dao.get(db, pk):
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
else:
|
||
if pk == request.user.id:
|
||
raise errors.ForbiddenError(msg='禁止修改自身后台管理登陆权限')
|
||
count = await user_dao.set_staff(db, pk)
|
||
return count
|
||
|
||
@staticmethod
|
||
async def update_status(*, request: Request, pk: int) -> int:
|
||
async with async_db_session.begin() as db:
|
||
await superuser_verify(request)
|
||
if not await user_dao.get(db, pk):
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
else:
|
||
if pk == request.user.id:
|
||
raise errors.ForbiddenError(msg='禁止修改自身状态')
|
||
count = await user_dao.set_status(db, pk)
|
||
return count
|
||
|
||
@staticmethod
|
||
async def update_multi_login(*, request: Request, pk: int) -> int:
|
||
async with async_db_session.begin() as db:
|
||
await superuser_verify(request)
|
||
if not await user_dao.get(db, pk):
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
else:
|
||
count = await user_dao.set_multi_login(db, pk)
|
||
token = await get_token(request)
|
||
user_id = request.user.id
|
||
latest_multi_login = await user_dao.get_multi_login(db, pk)
|
||
# TODO: 删除用户 refresh token, 此操作需要传参,暂时不考虑实现
|
||
# 当前用户修改自身时(普通/超级),除当前token外,其他token失效
|
||
if pk == user_id:
|
||
if not latest_multi_login:
|
||
prefix = f'{settings.TOKEN_REDIS_PREFIX}:{pk}:'
|
||
await redis_client.delete_prefix(prefix, exclude=prefix + token)
|
||
# 超级用户修改他人时,他人token将全部失效
|
||
else:
|
||
if not latest_multi_login:
|
||
prefix = f'{settings.TOKEN_REDIS_PREFIX}:{pk}:'
|
||
await redis_client.delete_prefix(prefix)
|
||
return count
|
||
|
||
@staticmethod
|
||
async def delete(*, username: str) -> int:
|
||
async with async_db_session.begin() as db:
|
||
input_user = await user_dao.get_by_username(db, username)
|
||
if not input_user:
|
||
raise errors.NotFoundError(msg='用户不存在')
|
||
count = await user_dao.delete(db, input_user.id)
|
||
prefix = [
|
||
f'{settings.TOKEN_REDIS_PREFIX}:{input_user.id}:',
|
||
f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{input_user.id}:',
|
||
]
|
||
for i in prefix:
|
||
await redis_client.delete_prefix(i)
|
||
return count
|
||
|
||
|
||
user_service: UserService = UserService()
|