Skip to content

Add department-related interfaces and others #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/app/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ APS_REDIS_PASSWORD=''
APS_REDIS_DATABASE=1
# Token
TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
# Opera Log
OPERA_LOG_ENCRYPT_SECRET_KEY='d77b25790a804c2b4a339dd0207941e4cefa5751935a33735bc73bb7071a005b'
6 changes: 3 additions & 3 deletions backend/app/api/v1/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

@router.post('/swagger_login', summary='swagger 表单登录', description='form 格式登录,仅用于 swagger 文档调试接口')
async def swagger_user_login(form_data: OAuth2PasswordRequestForm = Depends()) -> GetSwaggerToken:
token, user = await AuthService().swagger_login(form_data)
token, user = await AuthService().swagger_login(form_data=form_data)
return GetSwaggerToken(access_token=token, user=user)


Expand All @@ -44,12 +44,12 @@ async def user_login(request: Request, obj: Auth, background_tasks: BackgroundTa

@router.post('/new_token', summary='创建新 token', dependencies=[DependsJwtAuth])
async def create_new_token(refresh_token: Annotated[str, Query(...)]):
access_token, access_expire = await AuthService.new_token(refresh_token)
access_token, access_expire = await AuthService.new_token(refresh_token=refresh_token)
data = GetNewToken(access_token=access_token, access_token_expire_time=access_expire)
return await response_base.success(data=data)


@router.post('/logout', summary='用户登出', dependencies=[DependsJwtAuth])
async def user_logout(request: Request):
await AuthService.logout(request)
await AuthService.logout(request=request)
return await response_base.success()
51 changes: 49 additions & 2 deletions backend/app/api/v1/dept.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,54 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from fastapi import APIRouter
from typing import Annotated

from fastapi import APIRouter, Query, Request

from backend.app.common.casbin_rbac import DependsRBAC
from backend.app.common.jwt import DependsJwtAuth
from backend.app.common.response.response_schema import response_base
from backend.app.schemas.dept import CreateDept, GetAllDept, UpdateDept
from backend.app.services.dept_service import DeptService
from backend.app.utils.serializers import select_to_json

router = APIRouter()

# TODO: 添加 dept 相关接口

@router.get('/{pk}', summary='获取部门详情', dependencies=[DependsJwtAuth])
async def get_dept(pk: int):
dept = await DeptService.get(pk=pk)
data = GetAllDept(**select_to_json(dept))
return await response_base.success(data=data)


@router.get('', summary='获取所有部门展示树', dependencies=[DependsJwtAuth])
async def get_all_depts(
name: Annotated[str | None, Query()] = None,
leader: Annotated[str | None, Query()] = None,
phone: Annotated[str | None, Query()] = None,
status: Annotated[bool | None, Query()] = None,
):
dept = await DeptService.get_select(name=name, leader=leader, phone=phone, status=status)
return await response_base.success(data=dept)


@router.post('', summary='创建部门', dependencies=[DependsRBAC])
async def create_dept(request: Request, obj: CreateDept):
await DeptService.create(obj=obj, user_id=request.user.id)
return await response_base.success()


@router.put('/{pk}', summary='更新部门', dependencies=[DependsRBAC])
async def update_dept(request: Request, pk: int, obj: UpdateDept):
count = await DeptService.update(pk=pk, obj=obj, user_id=request.user.id)
if count > 0:
return await response_base.success()
return await response_base.fail()


@router.delete('{pk}', summary='删除部门', dependencies=[DependsRBAC])
async def delete_dept(pk: int):
count = await DeptService.delete(pk=pk)
if count > 0:
return await response_base.success()
return await response_base.fail()
2 changes: 1 addition & 1 deletion backend/app/api/v1/login_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def get_all_login_logs(

@router.delete('', summary='(批量)删除登录日志', dependencies=[DependsRBAC])
async def delete_login_log(pk: Annotated[list[int], Query(...)]):
count = await LoginLogService.delete(pk)
count = await LoginLogService.delete(pk=pk)
if count > 0:
return await response_base.success()
return await response_base.fail()
Expand Down
2 changes: 1 addition & 1 deletion backend/app/api/v1/opera_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def get_all_opera_logs(

@router.delete('', summary='(批量)删除操作日志', dependencies=[DependsRBAC])
async def delete_opera_log(pk: Annotated[list[int], Query(...)]):
count = await OperaLogService.delete(pk)
count = await OperaLogService.delete(pk=pk)
if count > 0:
return await response_base.success()
return await response_base.fail()
Expand Down
10 changes: 5 additions & 5 deletions backend/app/api/v1/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@

@router.post('/register', summary='用户注册')
async def user_register(obj: CreateUser):
await UserService.register(obj)
await UserService.register(obj=obj)
return await response_base.success()


@router.post('/password/reset', summary='密码重置')
async def password_reset(obj: ResetPassword):
count = await UserService.pwd_reset(obj)
@router.post('/password/reset', summary='密码重置', dependencies=[DependsJwtAuth])
async def password_reset(request: Request, obj: ResetPassword):
count = await UserService.pwd_reset(request=request, obj=obj)
if count > 0:
return await response_base.success()
return await response_base.fail()


@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtAuth])
async def get_user(username: str):
current_user = await UserService.get_userinfo(username)
current_user = await UserService.get_userinfo(username=username)
data = GetAllUserInfo(**select_to_json(current_user))
return await response_base.success(data=data)

Expand Down
15 changes: 15 additions & 0 deletions backend/app/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,18 @@ class LoginLogStatus(IntEnum):

fail = 0
success = 1


class BuildTreeType(StrEnum):
"""构建树形结构类型"""

traversal = 'traversal'
recursive = 'recursive'


class OperaLogCipherType(IntEnum):
"""操作日志加密类型"""

aes = 0
md5 = 1
plan = 2
9 changes: 7 additions & 2 deletions backend/app/core/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ class Settings(BaseSettings):
APS_REDIS_DATABASE: int

# Env Token
TOKEN_SECRET_KEY: str # 密钥 secrets.token_urlsafe(32))
TOKEN_SECRET_KEY: str # 密钥 secrets.token_urlsafe(32)

# Env Opera Log
OPERA_LOG_ENCRYPT_SECRET_KEY: str # 密钥 os.urandom(32), 需使用 bytes.hex() 方法转换为 str

# FastAPI
API_V1_STR: str = '/v1'
Expand Down Expand Up @@ -95,7 +98,7 @@ def validator_api_url(cls, values):

# Casbin
CASBIN_RBAC_MODEL_NAME: str = 'rbac_model.conf'
CASBIN_EXCLUDE: list[dict[str, str], dict[str, str]] = [
CASBIN_EXCLUDE: list[dict[str, str]] = [
{'method': 'POST', 'path': '/v1/auth/swagger_login'},
{'method': 'POST', 'path': '/v1/auth/login'},
{'method': 'POST', 'path': '/v1/auth/register'},
Expand All @@ -109,6 +112,8 @@ def validator_api_url(cls, values):
OPENAPI_URL,
'/v1/auth/swagger_login',
]
OPERA_LOG_ENCRYPT: int = 1 # 请求入参加密, 0: AES (高性能损耗), 1: md5, 2: 不加密, other: 替换为 ******
OPERA_LOG_ENCRYPT_INCLUDE: list[str] = ['password', 'old_password', 'new_password', 'confirm_password']

class Config:
# https://docs.pydantic.dev/usage/settings/#dotenv-env-support
Expand Down
51 changes: 38 additions & 13 deletions backend/app/crud/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Any, Dict, Generic, Type, TypeVar, NoReturn

from pydantic import BaseModel
from sqlalchemy import select, update, delete
from sqlalchemy import select, update, delete, and_
from sqlalchemy.ext.asyncio import AsyncSession

from backend.app.database.base_class import MappedBase
Expand All @@ -17,16 +17,36 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
def __init__(self, model: Type[ModelType]):
self.model = model

async def get_(self, db: AsyncSession, pk: int) -> ModelType | None:
async def get_(
self,
db: AsyncSession,
*,
pk: int | None = None,
name: str | None = None,
status: int | None = None,
del_flag: int | None = None,
) -> ModelType | None:
"""
通过主键 id 获取一条数据
通过主键 id 或者 name 获取一条数据

:param db:
:param pk:
:param name:
:param status:
:param del_flag:
:return:
"""
model = await db.execute(select(self.model).where(self.model.id == pk))
return model.scalars().first()
assert pk is not None or name is not None, '查询错误, pk 和 name 参数不能同时为空'
where_list = [self.model.id == pk] if pk is not None else [self.model.name == name]
if status is not None:
assert status in (0, 1), '查询错误, status 参数只能为 0 或 1'
where_list.append(self.model.status == status)
if del_flag is not None:
assert del_flag in (0, 1), '查询错误, del_flag 参数只能为 0 或 1'
where_list.append(self.model.del_flag == del_flag)

result = await db.execute(select(self.model).where(and_(*where_list)))
return result.scalars().first()

async def create_(self, db: AsyncSession, obj_in: CreateSchemaType, user_id: int | None = None) -> NoReturn:
"""
Expand All @@ -38,10 +58,10 @@ async def create_(self, db: AsyncSession, obj_in: CreateSchemaType, user_id: int
:return:
"""
if user_id:
db_obj = self.model(**obj_in.dict(), create_user=user_id)
create_data = self.model(**obj_in.dict(), create_user=user_id)
else:
db_obj = self.model(**obj_in.dict())
db.add(db_obj)
create_data = self.model(**obj_in.dict())
db.add(create_data)

async def update_(
self, db: AsyncSession, pk: int, obj_in: UpdateSchemaType | Dict[str, Any], user_id: int | None = None
Expand All @@ -61,16 +81,21 @@ async def update_(
update_data = obj_in.dict(exclude_unset=True)
if user_id:
update_data.update({'update_user': user_id})
model = await db.execute(update(self.model).where(self.model.id == pk).values(**update_data))
return model.rowcount
result = await db.execute(update(self.model).where(self.model.id == pk).values(**update_data))
return result.rowcount

async def delete_(self, db: AsyncSession, pk: int) -> int:
async def delete_(self, db: AsyncSession, pk: int, *, del_flag: int | None = None) -> int:
"""
通过主键 id 删除一条数据

:param db:
:param pk:
:param del_flag:
:return:
"""
model = await db.execute(delete(self.model).where(self.model.id == pk))
return model.rowcount
if del_flag is None:
result = await db.execute(delete(self.model).where(self.model.id == pk))
else:
assert del_flag == 1, '删除错误, del_flag 参数只能为 1'
result = await db.execute(update(self.model).where(self.model.id == pk).values(del_flag=del_flag))
return result.rowcount
2 changes: 1 addition & 1 deletion backend/app/crud/crud_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

class CRUDApi(CRUDBase[Api, CreateApi, UpdateApi]):
async def get(self, db: AsyncSession, pk: int) -> Api | None:
return await self.get_(db, pk)
return await self.get_(db, pk=pk)

async def get_all(self, name: str = None, method: str = None, path: str = None) -> Select:
se = select(self.model).order_by(desc(self.model.created_time))
Expand Down
53 changes: 51 additions & 2 deletions backend/app/crud/crud_dept.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Any

from sqlalchemy import select, desc, and_
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from backend.app.crud.base import CRUDBase
from backend.app.models import Dept
from backend.app.schemas.dept import CreateDept, UpdateDept


class CRUDDept(CRUDBase[Dept, CreateDept, UpdateDept]):
async def get(self, db, dept_id: int):
return await self.get_(db, dept_id)
async def get(self, db: AsyncSession, dept_id: int) -> Dept | None:
return await self.get_(db, pk=dept_id, del_flag=0)

async def get_by_name(self, db: AsyncSession, name: str) -> Dept | None:
return await self.get_(db, name=name)

async def get_all(
self, db: AsyncSession, name: str = None, leader: str = None, phone: str = None, status: bool = None
) -> Any:
se = select(self.model).order_by(desc(self.model.sort))
where_list = [self.model.del_flag == 0]
if name:
where_list.append(self.model.name.like(f'%{name}%'))
if leader:
where_list.append(self.model.leader.like(f'%{leader}%'))
if phone:
where_list.append(self.model.phone.startswith(phone))
if status is not None:
where_list.append(self.model.status == status)
if where_list:
se = se.where(and_(*where_list))
dept = await db.execute(se)
return dept.scalars().all()

async def create(self, db: AsyncSession, obj_in: dict) -> None:
obj = self.model(**obj_in)
db.add(obj)

async def update(self, db: AsyncSession, dept_id: int, obj_in: dict) -> int:
return await self.update_(db, dept_id, obj_in)

async def delete(self, db: AsyncSession, dept_id: int) -> int:
return await self.delete_(db, dept_id, del_flag=1)

async def get_user_relation(self, db: AsyncSession, dept_id: int) -> Any:
result = await db.execute(
select(self.model).options(selectinload(self.model.users)).where(self.model.id == dept_id)
)
user_relation = result.scalars().first()
return user_relation.users

async def get_children(self, db: AsyncSession, dept_id: int) -> Any:
result = await db.execute(select(self.model).where(self.model.id == dept_id))
dept = result.scalars().first()
return dept.children


DeptDao: CRUDDept = CRUDDept(Dept)
2 changes: 1 addition & 1 deletion backend/app/crud/crud_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class CRUDMenu(CRUDBase[Menu, CreateMenu, UpdateMenu]):
async def get(self, db, menu_id: int) -> Menu | None:
return await self.get_(db, menu_id)
return await self.get_(db, pk=menu_id)


MenuDao: CRUDMenu = CRUDMenu(Menu)
2 changes: 1 addition & 1 deletion backend/app/crud/crud_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

class CRUDRole(CRUDBase[Role, CreateRole, UpdateRole]):
async def get(self, db, role_id: int) -> Role | None:
return await self.get_(db, role_id)
return await self.get_(db, pk=role_id)

async def get_with_relation(self, db, role_id: int) -> Role | None:
role = await db.execute(
Expand Down
2 changes: 1 addition & 1 deletion backend/app/crud/crud_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

class CRUDUser(CRUDBase[User, CreateUser, UpdateUser]):
async def get(self, db: AsyncSession, user_id: int) -> User | None:
return await self.get_(db, user_id)
return await self.get_(db, pk=user_id)

async def get_by_username(self, db: AsyncSession, username: str) -> User | None:
user = await db.execute(select(self.model).where(self.model.username == username))
Expand Down
Loading