mirror of
https://github.com/fastapi-practices/fastapi-best-architecture.git
synced 2026-09-21 13:12:24 +00:00
Refactor global datetime to timezone datetime (#152)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Query, Request
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from backend.app.common.casbin_rbac import DependsRBAC
|
||||
from backend.app.common.pagination import PageDepends, paging_data
|
||||
@@ -33,14 +33,14 @@ async def get_all_apis(
|
||||
|
||||
|
||||
@router.post('', summary='创建接口', dependencies=[DependsRBAC])
|
||||
async def create_api(request: Request, obj: CreateApi):
|
||||
await ApiService.create(obj=obj, user_id=request.user.id)
|
||||
async def create_api(obj: CreateApi):
|
||||
await ApiService.create(obj=obj)
|
||||
return await response_base.success()
|
||||
|
||||
|
||||
@router.put('/{pk}', summary='更新接口', dependencies=[DependsRBAC])
|
||||
async def update_api(request: Request, pk: int, obj: UpdateApi):
|
||||
count = await ApiService.update(pk=pk, obj=obj, user_id=request.user.id)
|
||||
async def update_api(pk: int, obj: UpdateApi):
|
||||
count = await ApiService.update(pk=pk, obj=obj)
|
||||
if count > 0:
|
||||
return await response_base.success()
|
||||
return await response_base.fail()
|
||||
|
||||
@@ -21,7 +21,7 @@ from backend.app.services.casbin_service import CasbinService
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get('', summary='(模糊条件)分页获取所有 casbin 规则', dependencies=[DependsRBAC, PageDepends])
|
||||
@router.get('', summary='(模糊条件)分页获取所有权限规则', dependencies=[DependsRBAC, PageDepends])
|
||||
async def get_all_casbin(
|
||||
db: CurrentSession,
|
||||
ptype: Annotated[str | None, Query()] = None,
|
||||
@@ -32,13 +32,13 @@ async def get_all_casbin(
|
||||
return await response_base.success(data=page_data)
|
||||
|
||||
|
||||
@router.get('/policy', summary='获取所有 P 规则', dependencies=[DependsRBAC])
|
||||
@router.get('/policy', summary='获取所有访问权限规则', dependencies=[DependsRBAC])
|
||||
async def get_all_policies():
|
||||
policies = await CasbinService.get_policy_list()
|
||||
return await response_base.success(data=policies)
|
||||
|
||||
|
||||
@router.post('/policy', summary='添加基于角色(主)/用户(次)的访问权限', dependencies=[DependsRBAC])
|
||||
@router.post('/policy', summary='添加访问权限', dependencies=[DependsRBAC])
|
||||
async def create_policy(p: CreatePolicy):
|
||||
"""
|
||||
p 规则:
|
||||
@@ -53,25 +53,25 @@ async def create_policy(p: CreatePolicy):
|
||||
return await response_base.success(data=data)
|
||||
|
||||
|
||||
@router.put('/policy', summary='更新基于角色(主)/用户(次)的访问权限', dependencies=[DependsRBAC])
|
||||
@router.put('/policy', summary='更新访问权限', dependencies=[DependsRBAC])
|
||||
async def update_policy(old: UpdatePolicy, new: UpdatePolicy):
|
||||
data = await CasbinService.update_policy(old=old, new=new)
|
||||
return await response_base.success(data=data)
|
||||
|
||||
|
||||
@router.delete('/policy', summary='删除基于角色(主)/用户的访问权限', dependencies=[DependsRBAC])
|
||||
@router.delete('/policy', summary='删除访问权限', dependencies=[DependsRBAC])
|
||||
async def delete_policy(p: DeletePolicy):
|
||||
data = await CasbinService.delete_policy(p=p)
|
||||
return await response_base.success(data=data)
|
||||
|
||||
|
||||
@router.get('/group', summary='获取所有 g 规则', dependencies=[DependsRBAC])
|
||||
@router.get('/group', summary='获取所有组访问权限规则', dependencies=[DependsRBAC])
|
||||
async def get_all_groups():
|
||||
data = await CasbinService.get_group_list()
|
||||
return await response_base.success(data=data)
|
||||
|
||||
|
||||
@router.post('/group', summary='添加基于用户组的访问权限', dependencies=[DependsRBAC])
|
||||
@router.post('/group', summary='添加组访问权限', dependencies=[DependsRBAC])
|
||||
async def create_group(g: CreateUserRole):
|
||||
"""
|
||||
g 规则 (**依赖 p 规则**):
|
||||
@@ -79,14 +79,14 @@ async def create_group(g: CreateUserRole):
|
||||
- 如果在 p 规则中添加了基于角色的访问权限, 则还需要在 g 规则中添加基于用户组的访问权限, 才能真正拥有访问权限<br>
|
||||
**格式**: 用户 uuid + 角色 role
|
||||
|
||||
- 如果在p策略中添加了基于用户的访问权限, 则不添加相应的 g 规则能直接拥有访问权限<br>
|
||||
- 如果在 p 策略中添加了基于用户的访问权限, 则不添加相应的 g 规则能直接拥有访问权限<br>
|
||||
但是拥有的不是用户角色的所有权限, 而只是单一的对应的 p 规则所添加的访问权限
|
||||
"""
|
||||
data = await CasbinService.create_group(g=g)
|
||||
return await response_base.success(data=data)
|
||||
|
||||
|
||||
@router.delete('/group', summary='删除基于用户组的访问权限', dependencies=[DependsRBAC])
|
||||
@router.delete('/group', summary='删除组访问权限', dependencies=[DependsRBAC])
|
||||
async def delete_group(g: DeleteUserRole):
|
||||
data = await CasbinService.delete_group(g=g)
|
||||
return await response_base.success(data=data)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Query, Request
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from backend.app.common.casbin_rbac import DependsRBAC
|
||||
from backend.app.common.response.response_schema import response_base
|
||||
@@ -33,14 +33,14 @@ async def get_all_depts(
|
||||
|
||||
|
||||
@router.post('', summary='创建部门', dependencies=[DependsRBAC])
|
||||
async def create_dept(request: Request, obj: CreateDept):
|
||||
await DeptService.create(obj=obj, user_id=request.user.id)
|
||||
async def create_dept(obj: CreateDept):
|
||||
await DeptService.create(obj=obj)
|
||||
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)
|
||||
async def update_dept(pk: int, obj: UpdateDept):
|
||||
count = await DeptService.update(pk=pk, obj=obj)
|
||||
if count > 0:
|
||||
return await response_base.success()
|
||||
return await response_base.fail()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Query, Request
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from backend.app.common.casbin_rbac import DependsRBAC
|
||||
from backend.app.common.pagination import PageDepends, paging_data
|
||||
@@ -35,14 +35,14 @@ async def get_all_dict_datas(
|
||||
|
||||
|
||||
@router.post('', summary='创建字典', dependencies=[DependsRBAC])
|
||||
async def create_dict_data(request: Request, obj: CreateDictData):
|
||||
await DictDataService.create(obj=obj, user_id=request.user.id)
|
||||
async def create_dict_data(obj: CreateDictData):
|
||||
await DictDataService.create(obj=obj)
|
||||
return await response_base.success()
|
||||
|
||||
|
||||
@router.put('/{pk}', summary='更新字典', dependencies=[DependsRBAC])
|
||||
async def update_dict_data(request: Request, pk: int, obj: UpdateDictData):
|
||||
count = await DictDataService.update(pk=pk, obj=obj, user_id=request.user.id)
|
||||
async def update_dict_data(pk: int, obj: UpdateDictData):
|
||||
count = await DictDataService.update(pk=pk, obj=obj)
|
||||
if count > 0:
|
||||
return await response_base.success()
|
||||
return await response_base.fail()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Query, Request
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from backend.app.common.casbin_rbac import DependsRBAC
|
||||
from backend.app.common.pagination import PageDepends, paging_data
|
||||
@@ -27,14 +27,14 @@ async def get_all_dict_types(
|
||||
|
||||
|
||||
@router.post('', summary='创建字典类型', dependencies=[DependsRBAC])
|
||||
async def create_dict_type(request: Request, obj: CreateDictType):
|
||||
await DictTypeService.create(obj=obj, user_id=request.user.id)
|
||||
async def create_dict_type(obj: CreateDictType):
|
||||
await DictTypeService.create(obj=obj)
|
||||
return await response_base.success()
|
||||
|
||||
|
||||
@router.put('/{pk}', summary='更新字典类型', dependencies=[DependsRBAC])
|
||||
async def update_dict_type(request: Request, pk: int, obj: UpdateDictType):
|
||||
count = await DictTypeService.update(pk=pk, obj=obj, user_id=request.user.id)
|
||||
async def update_dict_type(pk: int, obj: UpdateDictType):
|
||||
count = await DictTypeService.update(pk=pk, obj=obj)
|
||||
if count > 0:
|
||||
return await response_base.success()
|
||||
return await response_base.fail()
|
||||
|
||||
@@ -36,14 +36,14 @@ async def get_all_menus(
|
||||
|
||||
|
||||
@router.post('', summary='创建菜单', dependencies=[DependsRBAC])
|
||||
async def create_menu(request: Request, obj: CreateMenu):
|
||||
await MenuService.create(obj=obj, user_id=request.user.id)
|
||||
async def create_menu(obj: CreateMenu):
|
||||
await MenuService.create(obj=obj)
|
||||
return await response_base.success()
|
||||
|
||||
|
||||
@router.put('/{pk}', summary='更新菜单', dependencies=[DependsRBAC])
|
||||
async def update_menu(request: Request, pk: int, obj: UpdateMenu):
|
||||
count = await MenuService.update(pk=pk, obj=obj, user_id=request.user.id)
|
||||
async def update_menu(pk: int, obj: UpdateMenu):
|
||||
count = await MenuService.update(pk=pk, obj=obj)
|
||||
if count > 0:
|
||||
return await response_base.success()
|
||||
return await response_base.fail()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Query, Request
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from backend.app.common.casbin_rbac import DependsRBAC
|
||||
from backend.app.common.pagination import PageDepends, paging_data
|
||||
@@ -34,14 +34,14 @@ async def get_all_roles(
|
||||
|
||||
|
||||
@router.post('', summary='创建角色', dependencies=[DependsRBAC])
|
||||
async def create_role(request: Request, obj: CreateRole):
|
||||
await RoleService.create(obj=obj, user_id=request.user.id)
|
||||
async def create_role(obj: CreateRole):
|
||||
await RoleService.create(obj=obj)
|
||||
return await response_base.success()
|
||||
|
||||
|
||||
@router.put('/{pk}', summary='更新角色', dependencies=[DependsRBAC])
|
||||
async def update_role(request: Request, pk: int, obj: UpdateRole):
|
||||
count = await RoleService.update(pk=pk, obj=obj, user_id=request.user.id)
|
||||
async def update_role(pk: int, obj: UpdateRole):
|
||||
count = await RoleService.update(pk=pk, obj=obj)
|
||||
if count > 0:
|
||||
return await response_base.success()
|
||||
return await response_base.fail()
|
||||
|
||||
@@ -61,7 +61,7 @@ class RBAC:
|
||||
raise AuthorizationError
|
||||
else:
|
||||
# casbin 权限校验
|
||||
user_uuid = request.user.user_uuid
|
||||
user_uuid = request.user.uuid
|
||||
enforcer = self.enforcer()
|
||||
if not enforcer.enforce(user_uuid, path, method):
|
||||
raise AuthorizationError
|
||||
|
||||
@@ -86,9 +86,7 @@ def register_exception(app: FastAPI):
|
||||
for error in raw_exc.errors()[:1]:
|
||||
field = str(error.get('loc')[-1])
|
||||
msg = error.get('msg')
|
||||
message += (
|
||||
f'{data.get(field, field) if field != "__root__" else ""} {msg}' + '.'
|
||||
)
|
||||
message += f'{data.get(field, field) if field != "__root__" else ""} {msg}' + '.'
|
||||
elif isinstance(raw_error.exc, json.JSONDecodeError):
|
||||
message += 'json解析失败'
|
||||
content = {
|
||||
|
||||
@@ -16,6 +16,7 @@ from backend.app.common.redis import redis_client
|
||||
from backend.app.core.conf import settings
|
||||
from backend.app.crud.crud_user import UserDao
|
||||
from backend.app.models import User
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
|
||||
|
||||
@@ -54,10 +55,10 @@ async def create_access_token(sub: str, expires_delta: timedelta | None = None,
|
||||
:return:
|
||||
"""
|
||||
if expires_delta:
|
||||
expire = datetime.now() + expires_delta
|
||||
expire = timezone_utils.get_timezone_expire_time(expires_delta)
|
||||
expire_seconds = int(expires_delta.total_seconds())
|
||||
else:
|
||||
expire = datetime.now() + timedelta(seconds=settings.TOKEN_EXPIRE_SECONDS)
|
||||
expire = timezone_utils.get_timezone_expire_time(timedelta(seconds=settings.TOKEN_EXPIRE_SECONDS))
|
||||
expire_seconds = settings.TOKEN_EXPIRE_SECONDS
|
||||
multi_login = kwargs.pop('multi_login', None)
|
||||
to_encode = {'exp': expire, 'sub': sub, **kwargs}
|
||||
@@ -80,9 +81,9 @@ async def create_refresh_token(sub: str, expire_time: datetime | None = None, **
|
||||
"""
|
||||
if expire_time:
|
||||
expire = expire_time + timedelta(seconds=settings.TOKEN_REFRESH_EXPIRE_SECONDS)
|
||||
expire_seconds = int((expire - datetime.now()).total_seconds())
|
||||
expire_seconds = timezone_utils.get_timezone_expire_seconds(expire_time)
|
||||
else:
|
||||
expire = datetime.now() + timedelta(seconds=settings.TOKEN_REFRESH_EXPIRE_SECONDS)
|
||||
expire = timezone_utils.get_timezone_expire_time(timedelta(seconds=settings.TOKEN_EXPIRE_SECONDS))
|
||||
expire_seconds = settings.TOKEN_REFRESH_EXPIRE_SECONDS
|
||||
multi_login = kwargs.pop('multi_login', None)
|
||||
to_encode = {'exp': expire, 'sub': sub, **kwargs}
|
||||
|
||||
@@ -8,7 +8,7 @@ class CustomCode(Enum):
|
||||
自定义错误码
|
||||
"""
|
||||
|
||||
CAPTCHA_ERROR = (40001, '图形验证码错误')
|
||||
CAPTCHA_ERROR = (40001, '验证码错误')
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
|
||||
@@ -6,7 +6,7 @@ from pydantic import BaseModel
|
||||
from sqlalchemy import select, update, delete, and_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from backend.app.database.base_class import MappedBase
|
||||
from backend.app.models.base import MappedBase
|
||||
|
||||
ModelType = TypeVar('ModelType', bound=MappedBase)
|
||||
CreateSchemaType = TypeVar('CreateSchemaType', bound=BaseModel)
|
||||
|
||||
@@ -31,11 +31,11 @@ class CRUDApi(CRUDBase[Api, CreateApi, UpdateApi]):
|
||||
api = await db.execute(select(self.model).where(self.model.name == name))
|
||||
return api.scalars().first()
|
||||
|
||||
async def create(self, db: AsyncSession, obj_in: CreateApi, user_id: int) -> NoReturn:
|
||||
await self.create_(db, obj_in, user_id)
|
||||
async def create(self, db: AsyncSession, obj_in: CreateApi) -> NoReturn:
|
||||
await self.create_(db, obj_in)
|
||||
|
||||
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateApi, user_id: int) -> int:
|
||||
return await self.update_(db, pk, obj_in, user_id)
|
||||
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateApi) -> int:
|
||||
return await self.update_(db, pk, obj_in)
|
||||
|
||||
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
|
||||
apis = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
|
||||
|
||||
@@ -36,13 +36,11 @@ class CRUDDept(CRUDBase[Dept, CreateDept, UpdateDept]):
|
||||
dept = await db.execute(se)
|
||||
return dept.scalars().all()
|
||||
|
||||
async def create(self, db: AsyncSession, obj_in: dict, user_id: int) -> None:
|
||||
obj_in.update({'create_user': user_id})
|
||||
obj = self.model(**obj_in)
|
||||
db.add(obj)
|
||||
async def create(self, db: AsyncSession, obj_in: CreateDept) -> None:
|
||||
await self.create_(db, obj_in)
|
||||
|
||||
async def update(self, db: AsyncSession, dept_id: int, obj_in: dict, user_id: int) -> int:
|
||||
return await self.update_(db, dept_id, obj_in, user_id=user_id)
|
||||
async def update(self, db: AsyncSession, dept_id: int, obj_in: UpdateDept) -> 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)
|
||||
|
||||
@@ -30,11 +30,11 @@ class CRUDDictData(CRUDBase[DictData, CreateDictData, UpdateDictData]):
|
||||
api = await db.execute(select(self.model).where(self.model.label == label))
|
||||
return api.scalars().first()
|
||||
|
||||
async def create(self, db: AsyncSession, obj_in: CreateDictData, user_id: int) -> None:
|
||||
await self.create_(db, obj_in, user_id)
|
||||
async def create(self, db: AsyncSession, obj_in: CreateDictData) -> None:
|
||||
await self.create_(db, obj_in)
|
||||
|
||||
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDictData, user_id: int) -> int:
|
||||
return await self.update_(db, pk, obj_in, user_id)
|
||||
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDictData) -> int:
|
||||
return await self.update_(db, pk, obj_in)
|
||||
|
||||
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
|
||||
apis = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
|
||||
|
||||
@@ -29,11 +29,11 @@ class CRUDDictType(CRUDBase[DictType, CreateDictType, UpdateDictType]):
|
||||
dept = await db.execute(select(self.model).where(self.model.code == code))
|
||||
return dept.scalars().first()
|
||||
|
||||
async def create(self, db: AsyncSession, obj_in: CreateDictType, user_id: int) -> None:
|
||||
await self.create_(db, obj_in, user_id)
|
||||
async def create(self, db: AsyncSession, obj_in: CreateDictType) -> None:
|
||||
await self.create_(db, obj_in)
|
||||
|
||||
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDictType, user_id: int) -> int:
|
||||
return await self.update_(db, pk, obj_in, user_id)
|
||||
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDictType) -> int:
|
||||
return await self.update_(db, pk, obj_in)
|
||||
|
||||
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
|
||||
apis = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
|
||||
|
||||
@@ -12,7 +12,7 @@ from backend.app.schemas.login_log import CreateLoginLog, UpdateLoginLog
|
||||
|
||||
class CRUDLoginLog(CRUDBase[LoginLog, CreateLoginLog, UpdateLoginLog]):
|
||||
async def get_all(self, username: str | None = None, status: int | None = None, ip: str | None = None) -> Select:
|
||||
se = select(self.model).order_by(desc(self.model.create_time))
|
||||
se = select(self.model).order_by(desc(self.model.created_time))
|
||||
where_list = []
|
||||
if username:
|
||||
where_list.append(self.model.username.like(f'%{username}%'))
|
||||
|
||||
@@ -38,13 +38,11 @@ class CRUDMenu(CRUDBase[Menu, CreateMenu, UpdateMenu]):
|
||||
menu = await db.execute(se)
|
||||
return menu.scalars().all()
|
||||
|
||||
async def create(self, db, obj_in: dict, user_id: int) -> None:
|
||||
obj_in.update({'create_user': user_id})
|
||||
obj = self.model(**obj_in)
|
||||
db.add(obj)
|
||||
async def create(self, db, obj_in: CreateMenu) -> None:
|
||||
await self.create_(db, obj_in)
|
||||
|
||||
async def update(self, db, menu_id: int, obj_in: dict, user_id: int) -> int:
|
||||
return await self.update_(db, menu_id, obj_in, user_id)
|
||||
async def update(self, db, menu_id: int, obj_in: UpdateMenu) -> int:
|
||||
return await self.update_(db, menu_id, obj_in)
|
||||
|
||||
async def delete(self, db, menu_id: int) -> int:
|
||||
return await self.delete_(db, menu_id)
|
||||
|
||||
@@ -12,7 +12,7 @@ from backend.app.schemas.opera_log import CreateOperaLog, UpdateOperaLog
|
||||
|
||||
class CRUDOperaLogDao(CRUDBase[OperaLog, CreateOperaLog, UpdateOperaLog]):
|
||||
async def get_all(self, username: str | None = None, status: int | None = None, ip: str | None = None) -> Select:
|
||||
se = select(self.model).order_by(desc(self.model.create_time))
|
||||
se = select(self.model).order_by(desc(self.model.created_time))
|
||||
where_list = []
|
||||
if username:
|
||||
where_list.append(self.model.username.like(f'%{username}%'))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import NoReturn
|
||||
|
||||
from sqlalchemy import select, update, delete
|
||||
from sqlalchemy import select, update, delete, desc
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from backend.app.crud.base import CRUDBase
|
||||
@@ -21,7 +21,7 @@ class CRUDRole(CRUDBase[Role, CreateRole, UpdateRole]):
|
||||
return role.scalars().first()
|
||||
|
||||
async def get_all(self, name: str = None, data_scope: int = None):
|
||||
se = select(self.model).options(selectinload(self.model.menus)).order_by(self.model.created_time.desc())
|
||||
se = select(self.model).options(selectinload(self.model.menus)).order_by(desc(self.model.created_time))
|
||||
where_list = []
|
||||
if name:
|
||||
where_list.append(self.model.name.like(f'%{name}%'))
|
||||
@@ -35,19 +35,17 @@ class CRUDRole(CRUDBase[Role, CreateRole, UpdateRole]):
|
||||
role = await db.execute(select(self.model).where(self.model.name == name))
|
||||
return role.scalars().first()
|
||||
|
||||
async def create(self, db, obj_in: CreateRole, user_id: int) -> NoReturn:
|
||||
new_role = self.model(**obj_in.dict(exclude={'menus'}), create_user=user_id)
|
||||
async def create(self, db, obj_in: CreateRole) -> NoReturn:
|
||||
new_role = self.model(**obj_in.dict(exclude={'menus'}))
|
||||
menu_list = []
|
||||
for menu_id in obj_in.menus:
|
||||
menu_list.append(await db.get(Menu, menu_id))
|
||||
new_role.menus.append(*menu_list)
|
||||
db.add(new_role)
|
||||
|
||||
async def update(self, db, role_id: int, obj_in: UpdateRole, user_id: int) -> int:
|
||||
async def update(self, db, role_id: int, obj_in: UpdateRole) -> int:
|
||||
role = await db.execute(
|
||||
update(self.model)
|
||||
.where(self.model.id == role_id)
|
||||
.values(**obj_in.dict(exclude={'menus'}), update_user=user_id)
|
||||
update(self.model).where(self.model.id == role_id).values(**obj_in.dict(exclude={'menus'}))
|
||||
)
|
||||
current_role = await self.get_with_relation(db, role_id)
|
||||
# 删除角色所有菜单
|
||||
|
||||
@@ -23,7 +23,9 @@ class CRUDUser(CRUDBase[User, CreateUser, UpdateUser]):
|
||||
return user.scalars().first()
|
||||
|
||||
async def update_login_time(self, db: AsyncSession, username: str, login_time: datetime) -> int:
|
||||
user = await db.execute(update(self.model).where(self.model.username == username).values(last_login=login_time))
|
||||
user = await db.execute(
|
||||
update(self.model).where(self.model.username == username).values(last_login_time=login_time)
|
||||
)
|
||||
await db.commit()
|
||||
return user.rowcount
|
||||
|
||||
@@ -72,7 +74,7 @@ class CRUDUser(CRUDBase[User, CreateUser, UpdateUser]):
|
||||
select(self.model)
|
||||
.options(selectinload(self.model.dept))
|
||||
.options(selectinload(self.model.roles).selectinload(Role.menus))
|
||||
.order_by(desc(self.model.time_joined))
|
||||
.order_by(desc(self.model.join_time))
|
||||
)
|
||||
where_list = []
|
||||
if username:
|
||||
|
||||
@@ -9,11 +9,7 @@ from typing_extensions import Annotated
|
||||
|
||||
from backend.app.common.log import log
|
||||
from backend.app.core.conf import settings
|
||||
from backend.app.database.base_class import MappedBase
|
||||
|
||||
"""
|
||||
说明:SqlAlchemy
|
||||
"""
|
||||
from backend.app.models.base import MappedBase
|
||||
|
||||
|
||||
def create_engine_and_session(url: str | URL):
|
||||
@@ -38,11 +34,7 @@ async_engine, async_db_session = create_engine_and_session(SQLALCHEMY_DATABASE_U
|
||||
|
||||
|
||||
async def get_db() -> AsyncSession:
|
||||
"""
|
||||
session 生成器
|
||||
|
||||
:return:
|
||||
"""
|
||||
"""session 生成器"""
|
||||
session = async_db_session()
|
||||
try:
|
||||
yield session
|
||||
@@ -58,8 +50,6 @@ CurrentSession = Annotated[AsyncSession, Depends(get_db)]
|
||||
|
||||
|
||||
async def create_table():
|
||||
"""
|
||||
创建数据库表
|
||||
"""
|
||||
"""创建数据库表"""
|
||||
async with async_engine.begin() as coon:
|
||||
await coon.run_sync(MappedBase.metadata.create_all)
|
||||
|
||||
@@ -21,15 +21,15 @@ class InitTestData:
|
||||
async def create_dept(self):
|
||||
"""自动创建部门"""
|
||||
async with self.session.begin() as db:
|
||||
department_obj = Dept(name='test', create_user=1)
|
||||
department_obj = Dept(name='test')
|
||||
db.add(department_obj)
|
||||
log.info('部门 test 创建成功')
|
||||
|
||||
async def create_role(self):
|
||||
"""自动创建角色"""
|
||||
async with self.session.begin() as db:
|
||||
role_obj = Role(name='test', create_user=1)
|
||||
role_obj.menus.append(Menu(name='test', create_user=1))
|
||||
role_obj = Role(name='test')
|
||||
role_obj.menus.append(Menu(name='test'))
|
||||
db.add(role_obj)
|
||||
log.info('角色 test 创建成功')
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
||||
|
||||
from backend.app.common.log import log
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
|
||||
class AccessMiddleware(BaseHTTPMiddleware):
|
||||
"""记录请求日志中间件"""
|
||||
|
||||
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
||||
start_time = datetime.now()
|
||||
start_time = timezone_utils.get_timezone_datetime()
|
||||
response = await call_next(request)
|
||||
end_time = datetime.now()
|
||||
end_time = timezone_utils.get_timezone_datetime()
|
||||
log.info(f'{response.status_code} {request.client.host} {request.method} {request.url} {end_time - start_time}')
|
||||
return response
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
@@ -16,6 +15,7 @@ from backend.app.schemas.opera_log import CreateOperaLog
|
||||
from backend.app.services.opera_log_service import OperaLogService
|
||||
from backend.app.utils.encrypt import AESCipher, Md5Cipher
|
||||
from backend.app.utils.request_parse import parse_user_agent_info, parse_ip_info
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
|
||||
class OperaLogMiddleware:
|
||||
@@ -59,9 +59,9 @@ class OperaLogMiddleware:
|
||||
request.state.device = device
|
||||
|
||||
# 执行请求
|
||||
start_time = datetime.now()
|
||||
start_time = timezone_utils.get_timezone_datetime()
|
||||
code, msg, status, err = await self.execute_request(request, send)
|
||||
end_time = datetime.now()
|
||||
end_time = timezone_utils.get_timezone_datetime()
|
||||
cost_time = (end_time - start_time).total_seconds() * 1000.0
|
||||
|
||||
router = request.scope.get('route')
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# 导入所有模型,并将 Base 放在最前面, 以便 Base 拥有它们
|
||||
# imported by Alembic
|
||||
"""
|
||||
from backend.app.database.base_class import MappedBase
|
||||
from backend.app.models.base import MappedBase
|
||||
from backend.app.models.sys_api import Api
|
||||
from backend.app.models.sys_casbin_rule import CasbinRule
|
||||
from backend.app.models.sys_dept import Dept
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, declared_attr, MappedAsDataclass
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
# 通用 Mapped 类型主键, 需手动添加,参考以下使用方式
|
||||
# MappedBase -> id: Mapped[id_key]
|
||||
# DataClassBase && Base -> id: Mapped[id_key] = mapped_column(init=False)
|
||||
id_key = Annotated[
|
||||
int, mapped_column(primary_key=True, index=True, autoincrement=True, sort_order=-9999, comment='主键id')
|
||||
int, mapped_column(primary_key=True, index=True, autoincrement=True, sort_order=-999, comment='主键id')
|
||||
]
|
||||
|
||||
|
||||
class _BaseMixin(MappedAsDataclass):
|
||||
"""
|
||||
Mixin 数据类
|
||||
# Mixin: 一种面向对象编程概念, 使结构变得更加清晰, `Wiki <https://en.wikipedia.org/wiki/Mixin/>`__
|
||||
class UserMixin(MappedAsDataclass):
|
||||
"""用户 Mixin 数据类"""
|
||||
|
||||
Mixin: 一种面向对象编程概念, 使结构变得更加清晰, `Wiki <https://en.wikipedia.org/wiki/Mixin/>`__
|
||||
"""
|
||||
create_user: Mapped[int] = mapped_column(sort_order=998, comment='创建者')
|
||||
update_user: Mapped[int | None] = mapped_column(init=False, default=None, sort_order=998, comment='修改者')
|
||||
|
||||
create_user: Mapped[int] = mapped_column(sort_order=9999, comment='创建者')
|
||||
update_user: Mapped[int | None] = mapped_column(init=False, default=None, sort_order=9999, comment='修改者')
|
||||
created_time: Mapped[datetime] = mapped_column(init=False, default=func.now(), sort_order=9999, comment='创建时间')
|
||||
|
||||
class DateTimeMixin(MappedAsDataclass):
|
||||
"""日期时间 Mixin 数据类"""
|
||||
|
||||
created_time: Mapped[datetime] = mapped_column(
|
||||
init=False, default_factory=timezone_utils.get_timezone_datetime, sort_order=999, comment='创建时间'
|
||||
)
|
||||
updated_time: Mapped[datetime | None] = mapped_column(
|
||||
init=False, onupdate=func.now(), sort_order=9999, comment='更新时间'
|
||||
init=False, onupdate=timezone_utils.get_timezone_datetime, sort_order=999, comment='更新时间'
|
||||
)
|
||||
|
||||
|
||||
@@ -53,18 +57,9 @@ class DataClassBase(MappedAsDataclass, MappedBase):
|
||||
__abstract__ = True
|
||||
|
||||
|
||||
class Base(DataClassBase, _BaseMixin):
|
||||
class Base(DataClassBase, DateTimeMixin):
|
||||
"""
|
||||
声明性 Mixin 数据类基类, 带有数据类集成, 并包含 MiXin 数据类基础表结构, 你可以简单的理解它为含有基础表结构的数据类基类
|
||||
""" # noqa: E501
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
|
||||
def use_uuid() -> str:
|
||||
"""
|
||||
使用uuid
|
||||
|
||||
:return:
|
||||
"""
|
||||
return uuid.uuid4().hex
|
||||
@@ -5,7 +5,7 @@ from sqlalchemy import String
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from backend.app.database.base_class import Base, id_key
|
||||
from backend.app.models.base import Base, id_key
|
||||
|
||||
|
||||
class Api(Base):
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from backend.app.database.base_class import id_key, MappedBase
|
||||
from backend.app.models.base import id_key, MappedBase
|
||||
|
||||
|
||||
class CasbinRule(MappedBase):
|
||||
"""
|
||||
重写 casbin_sqlalchemy_adapter 中的 casbinRule model类, 使用自定义 MappedBase, 避免产生 alembic 迁移问题
|
||||
"""
|
||||
"""重写 casbin 中的 casbinRule model 类, 使用自定义 Base, 避免产生 alembic 迁移问题"""
|
||||
|
||||
__tablename__ = 'sys_casbin_rule'
|
||||
|
||||
@@ -23,3 +20,14 @@ class CasbinRule(MappedBase):
|
||||
v3: Mapped[str | None] = mapped_column(String(255))
|
||||
v4: Mapped[str | None] = mapped_column(String(255))
|
||||
v5: Mapped[str | None] = mapped_column(String(255))
|
||||
|
||||
def __str__(self):
|
||||
arr = [self.ptype]
|
||||
for v in (self.v0, self.v1, self.v2, self.v3, self.v4, self.v5):
|
||||
if v is None:
|
||||
break
|
||||
arr.append(v)
|
||||
return ', '.join(arr)
|
||||
|
||||
def __repr__(self):
|
||||
return '<CasbinRule {}: "{}">'.format(self.id, str(self))
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Union
|
||||
from sqlalchemy import String, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from backend.app.database.base_class import Base, id_key
|
||||
from backend.app.models.base import Base, id_key
|
||||
|
||||
|
||||
class Dept(Base):
|
||||
|
||||
@@ -4,7 +4,7 @@ from sqlalchemy import String, ForeignKey
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from backend.app.database.base_class import Base, id_key
|
||||
from backend.app.models.base import Base, id_key
|
||||
|
||||
|
||||
class DictData(Base):
|
||||
|
||||
@@ -4,7 +4,7 @@ from sqlalchemy import String
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from backend.app.database.base_class import Base, id_key
|
||||
from backend.app.models.base import Base, id_key
|
||||
|
||||
|
||||
class DictType(Base):
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import String, func
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from backend.app.database.base_class import DataClassBase, id_key
|
||||
from backend.app.models.base import DataClassBase, id_key
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
|
||||
class LoginLog(DataClassBase):
|
||||
@@ -28,4 +29,6 @@ class LoginLog(DataClassBase):
|
||||
device: Mapped[str | None] = mapped_column(String(50), comment='设备')
|
||||
msg: Mapped[str] = mapped_column(LONGTEXT, comment='提示消息')
|
||||
login_time: Mapped[datetime] = mapped_column(comment='登录时间')
|
||||
create_time: Mapped[datetime] = mapped_column(init=False, default=func.now(), comment='创建时间')
|
||||
created_time: Mapped[datetime] = mapped_column(
|
||||
init=False, default_factory=timezone_utils.get_timezone_datetime, comment='创建时间'
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ from sqlalchemy import String, ForeignKey
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from backend.app.database.base_class import Base, id_key
|
||||
from backend.app.models.base import Base, id_key
|
||||
from backend.app.models.sys_role_menu import sys_role_menu
|
||||
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import String, func
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.dialects.mysql import JSON, LONGTEXT
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from backend.app.database.base_class import DataClassBase, id_key
|
||||
from backend.app.models.base import DataClassBase, id_key
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
|
||||
class OperaLog(DataClassBase):
|
||||
@@ -33,4 +34,6 @@ class OperaLog(DataClassBase):
|
||||
msg: Mapped[str | None] = mapped_column(LONGTEXT, comment='提示消息')
|
||||
cost_time: Mapped[float] = mapped_column(insert_default=0.0, comment='请求耗时ms')
|
||||
opera_time: Mapped[datetime] = mapped_column(comment='操作时间')
|
||||
create_time: Mapped[datetime] = mapped_column(init=False, default=func.now(), comment='创建时间')
|
||||
created_time: Mapped[datetime] = mapped_column(
|
||||
init=False, default_factory=timezone_utils.get_timezone_datetime, comment='创建时间'
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from sqlalchemy import String
|
||||
from sqlalchemy.dialects.mysql import LONGTEXT
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from backend.app.database.base_class import Base, id_key
|
||||
from backend.app.models.base import Base, id_key
|
||||
from backend.app.models.sys_role_menu import sys_role_menu
|
||||
from backend.app.models.sys_user_role import sys_user_role
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from sqlalchemy import Table, Column, ForeignKey, INT, Integer
|
||||
|
||||
from backend.app.database.base_class import MappedBase
|
||||
from backend.app.models.base import MappedBase
|
||||
|
||||
sys_role_menu = Table(
|
||||
'sys_role_menu',
|
||||
|
||||
@@ -2,21 +2,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import func, String, ForeignKey
|
||||
from sqlalchemy import String, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from backend.app.database.base_class import use_uuid, id_key, DataClassBase
|
||||
from backend.app.models.base import id_key, Base
|
||||
from backend.app.models.sys_user_role import sys_user_role
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
|
||||
class User(DataClassBase):
|
||||
class User(Base):
|
||||
"""用户表"""
|
||||
|
||||
__tablename__ = 'sys_user'
|
||||
|
||||
id: Mapped[id_key] = mapped_column(init=False)
|
||||
user_uuid: Mapped[str] = mapped_column(String(50), init=False, insert_default=use_uuid, unique=True)
|
||||
uuid: Mapped[str] = mapped_column(String(50), init=False, default_factory=uuid4, unique=True)
|
||||
username: Mapped[str] = mapped_column(String(20), unique=True, index=True, comment='用户名')
|
||||
nickname: Mapped[str] = mapped_column(String(20), unique=True, comment='昵称')
|
||||
password: Mapped[str] = mapped_column(String(255), comment='密码')
|
||||
@@ -26,8 +28,12 @@ class User(DataClassBase):
|
||||
is_multi_login: Mapped[bool] = mapped_column(default=False, comment='是否重复登陆(0否 1是)')
|
||||
avatar: Mapped[str | None] = mapped_column(String(255), default=None, comment='头像')
|
||||
phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机号')
|
||||
time_joined: Mapped[datetime] = mapped_column(init=False, default=func.now(), comment='注册时间')
|
||||
last_login: Mapped[datetime | None] = mapped_column(init=False, onupdate=func.now(), comment='上次登录')
|
||||
join_time: Mapped[datetime] = mapped_column(
|
||||
init=False, default_factory=timezone_utils.get_timezone_datetime, comment='注册时间'
|
||||
)
|
||||
last_login_time: Mapped[datetime | None] = mapped_column(
|
||||
init=False, onupdate=timezone_utils.get_timezone_datetime, comment='上次登录'
|
||||
)
|
||||
# 部门用户一对多
|
||||
dept_id: Mapped[int | None] = mapped_column(
|
||||
ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, comment='部门关联ID'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from sqlalchemy import Table, Column, ForeignKey, INT, Integer
|
||||
|
||||
from backend.app.database.base_class import MappedBase
|
||||
from backend.app.models.base import MappedBase
|
||||
|
||||
sys_user_role = Table(
|
||||
'sys_user_role',
|
||||
|
||||
@@ -31,8 +31,6 @@ class UpdateApi(ApiBase):
|
||||
|
||||
class GetAllApi(ApiBase):
|
||||
id: int
|
||||
create_user: int
|
||||
update_user: int = None
|
||||
created_time: datetime
|
||||
updated_time: datetime | None = None
|
||||
|
||||
|
||||
@@ -48,8 +48,6 @@ class UpdateDept(DeptBase):
|
||||
class GetAllDept(DeptBase):
|
||||
id: int
|
||||
del_flag: bool
|
||||
create_user: int
|
||||
update_user: int = None
|
||||
created_time: datetime
|
||||
updated_time: datetime | None = None
|
||||
|
||||
|
||||
@@ -29,8 +29,6 @@ class UpdateDictData(DictDataBase):
|
||||
class GetAllDictData(DictDataBase):
|
||||
id: int
|
||||
type: GetAllDictType
|
||||
create_user: int
|
||||
update_user: int = None
|
||||
created_time: datetime
|
||||
updated_time: datetime | None = None
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ class UpdateDictType(DictTypeBase):
|
||||
|
||||
class GetAllDictType(DictTypeBase):
|
||||
id: int
|
||||
create_user: int
|
||||
update_user: int = None
|
||||
created_time: datetime
|
||||
updated_time: datetime | None = None
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
from backend.app.schemas.base import SchemaBase
|
||||
|
||||
|
||||
@@ -32,7 +31,7 @@ class UpdateLoginLog(LoginLogBase):
|
||||
|
||||
class GetAllLoginLog(LoginLogBase):
|
||||
id: int
|
||||
create_time: datetime
|
||||
created_time: datetime
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -31,8 +31,6 @@ class UpdateMenu(MenuBase):
|
||||
|
||||
class GetAllMenu(MenuBase):
|
||||
id: int
|
||||
create_user: int
|
||||
update_user: int = None
|
||||
created_time: datetime
|
||||
updated_time: datetime | None = None
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class UpdateOperaLog(OperaLogBase):
|
||||
|
||||
class GetAllOperaLog(OperaLogBase):
|
||||
id: int
|
||||
create_time: datetime
|
||||
created_time: datetime
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -26,8 +26,6 @@ class UpdateRole(RoleBase):
|
||||
|
||||
class GetAllRole(RoleBase):
|
||||
id: int
|
||||
create_user: int
|
||||
update_user: int = None
|
||||
created_time: datetime
|
||||
updated_time: datetime | None = None
|
||||
menus: list[GetAllMenu]
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
from backend.app.schemas.base import SchemaBase
|
||||
from backend.app.schemas.user import GetUserInfoNoRelation
|
||||
|
||||
|
||||
@@ -68,13 +68,13 @@ class Avatar(SchemaBase):
|
||||
class GetUserInfoNoRelation(_UserInfoBase):
|
||||
dept_id: int | None = None
|
||||
id: int
|
||||
user_uuid: str
|
||||
uuid: str
|
||||
avatar: str | None = None
|
||||
status: StatusType = Field(default=StatusType.enable)
|
||||
is_superuser: bool
|
||||
is_multi_login: bool
|
||||
time_joined: datetime = None
|
||||
last_login: datetime | None = None
|
||||
join_time: datetime = None
|
||||
last_login_time: datetime | None = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
@@ -23,17 +23,17 @@ class ApiService:
|
||||
return await ApiDao.get_all(name=name, method=method, path=path)
|
||||
|
||||
@staticmethod
|
||||
async def create(*, obj: CreateApi, user_id: int) -> None:
|
||||
async def create(*, obj: CreateApi) -> None:
|
||||
async with async_db_session.begin() as db:
|
||||
api = await ApiDao.get_by_name(db, obj.name)
|
||||
if api:
|
||||
raise errors.ForbiddenError(msg='接口已存在')
|
||||
await ApiDao.create(db, obj, user_id)
|
||||
await ApiDao.create(db, obj)
|
||||
|
||||
@staticmethod
|
||||
async def update(*, pk: int, obj: UpdateApi, user_id: int) -> int:
|
||||
async def update(*, pk: int, obj: UpdateApi) -> int:
|
||||
async with async_db_session.begin() as db:
|
||||
count = await ApiDao.update(db, pk, obj, user_id)
|
||||
count = await ApiDao.update(db, pk, obj)
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -5,7 +5,6 @@ from typing import NoReturn
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from pydantic.datetime_parse import parse_datetime
|
||||
from starlette.background import BackgroundTasks, BackgroundTask
|
||||
|
||||
from backend.app.common import jwt
|
||||
@@ -19,10 +18,11 @@ from backend.app.crud.crud_user import UserDao
|
||||
from backend.app.database.db_mysql import async_db_session
|
||||
from backend.app.schemas.user import AuthLogin
|
||||
from backend.app.services.login_log_service import LoginLogService
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
|
||||
class AuthService:
|
||||
login_time = parse_datetime(datetime.now())
|
||||
login_time = timezone_utils.get_timezone_datetime()
|
||||
|
||||
async def swagger_login(self, *, form_data: OAuth2PasswordRequestForm):
|
||||
async with async_db_session() as db:
|
||||
@@ -89,6 +89,7 @@ class AuthService:
|
||||
msg='登录成功',
|
||||
)
|
||||
background_tasks.add_task(LoginLogService.create, **log_info)
|
||||
await redis_client.delete(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
|
||||
return access_token, refresh_token, access_token_expire_time, refresh_token_expire_time, user
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -28,7 +28,7 @@ class DeptService:
|
||||
return tree_data
|
||||
|
||||
@staticmethod
|
||||
async def create(*, obj: CreateDept, user_id: int):
|
||||
async def create(*, obj: CreateDept):
|
||||
async with async_db_session.begin() as db:
|
||||
dept = await DeptDao.get_by_name(db, obj.name)
|
||||
if dept:
|
||||
@@ -37,11 +37,10 @@ class DeptService:
|
||||
parent_dept = await DeptDao.get(db, obj.parent_id)
|
||||
if not parent_dept:
|
||||
raise errors.NotFoundError(msg='父级部门不存在')
|
||||
new_obj = obj.dict()
|
||||
await DeptDao.create(db, new_obj, user_id)
|
||||
await DeptDao.create(db, obj)
|
||||
|
||||
@staticmethod
|
||||
async def update(*, pk: int, obj: UpdateDept, user_id: int):
|
||||
async def update(*, pk: int, obj: UpdateDept):
|
||||
async with async_db_session.begin() as db:
|
||||
dept = await DeptDao.get(db, pk)
|
||||
if not dept:
|
||||
@@ -53,8 +52,7 @@ class DeptService:
|
||||
parent_dept = await DeptDao.get(db, obj.parent_id)
|
||||
if not parent_dept:
|
||||
raise errors.NotFoundError(msg='父级部门不存在')
|
||||
new_obj = obj.dict()
|
||||
count = await DeptDao.update(db, pk, new_obj, user_id)
|
||||
count = await DeptDao.update(db, pk, obj)
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -24,7 +24,7 @@ class DictDataService:
|
||||
return await DictDataDao.get_all(label=label, value=value, status=status)
|
||||
|
||||
@staticmethod
|
||||
async def create(*, obj: CreateDictData, user_id: int) -> None:
|
||||
async def create(*, obj: CreateDictData) -> None:
|
||||
async with async_db_session.begin() as db:
|
||||
dict_data = await DictDataDao.get_by_label(db, obj.label)
|
||||
if dict_data:
|
||||
@@ -32,10 +32,10 @@ class DictDataService:
|
||||
dict_type = await DictTypeDao.get(db, obj.type_id)
|
||||
if not dict_type:
|
||||
raise errors.ForbiddenError(msg='字典类型不存在')
|
||||
await DictDataDao.create(db, obj, user_id)
|
||||
await DictDataDao.create(db, obj)
|
||||
|
||||
@staticmethod
|
||||
async def update(*, pk: int, obj: UpdateDictData, user_id: int) -> int:
|
||||
async def update(*, pk: int, obj: UpdateDictData) -> int:
|
||||
async with async_db_session.begin() as db:
|
||||
dict_data = await DictDataDao.get(db, pk)
|
||||
if not dict_data:
|
||||
@@ -46,7 +46,7 @@ class DictDataService:
|
||||
dict_type = await DictTypeDao.get(db, obj.type_id)
|
||||
if not dict_type:
|
||||
raise errors.ForbiddenError(msg='字典类型不存在')
|
||||
count = await DictDataDao.update(db, pk, obj, user_id)
|
||||
count = await DictDataDao.update(db, pk, obj)
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -14,15 +14,15 @@ class DictTypeService:
|
||||
return await DictTypeDao.get_all(name=name, code=code, status=status)
|
||||
|
||||
@staticmethod
|
||||
async def create(*, obj: CreateDictType, user_id: int) -> None:
|
||||
async def create(*, obj: CreateDictType) -> None:
|
||||
async with async_db_session.begin() as db:
|
||||
dict_type = await DictTypeDao.get_by_code(db, obj.code)
|
||||
if dict_type:
|
||||
raise errors.ForbiddenError(msg='字典类型已存在')
|
||||
await DictTypeDao.create(db, obj, user_id)
|
||||
await DictTypeDao.create(db, obj)
|
||||
|
||||
@staticmethod
|
||||
async def update(*, pk: int, obj: UpdateDictType, user_id: int) -> int:
|
||||
async def update(*, pk: int, obj: UpdateDictType) -> int:
|
||||
async with async_db_session.begin() as db:
|
||||
dict_type = await DictTypeDao.get(db, pk)
|
||||
if not dict_type:
|
||||
@@ -30,7 +30,7 @@ class DictTypeService:
|
||||
if dict_type.code != obj.code:
|
||||
if await DictTypeDao.get_by_code(db, obj.code):
|
||||
raise errors.ForbiddenError(msg='字典类型已存在')
|
||||
count = await DictTypeDao.update(db, pk, obj, user_id)
|
||||
count = await DictTypeDao.update(db, pk, obj)
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -26,7 +26,7 @@ class LoginLogService:
|
||||
try:
|
||||
# request.state 来自 opera log 中间件定义的扩展参数,详见 opera_log_middleware.py
|
||||
obj_in = CreateLoginLog(
|
||||
user_uuid=user.user_uuid,
|
||||
user_uuid=user.uuid,
|
||||
username=user.username,
|
||||
status=status,
|
||||
ip=request.state.ip,
|
||||
|
||||
@@ -36,7 +36,7 @@ class MenuService:
|
||||
return menu_tree
|
||||
|
||||
@staticmethod
|
||||
async def create(*, obj: CreateMenu, user_id: int):
|
||||
async def create(*, obj: CreateMenu):
|
||||
async with async_db_session.begin() as db:
|
||||
menu = await MenuDao.get_by_name(db, obj.name)
|
||||
if menu:
|
||||
@@ -45,11 +45,10 @@ class MenuService:
|
||||
parent_menu = await MenuDao.get(db, obj.parent_id)
|
||||
if not parent_menu:
|
||||
raise errors.NotFoundError(msg='父级菜单不存在')
|
||||
new_obj = obj.dict()
|
||||
await MenuDao.create(db, new_obj, user_id)
|
||||
await MenuDao.create(db, obj)
|
||||
|
||||
@staticmethod
|
||||
async def update(*, pk: int, obj: UpdateMenu, user_id: int):
|
||||
async def update(*, pk: int, obj: UpdateMenu):
|
||||
async with async_db_session.begin() as db:
|
||||
menu = await MenuDao.get(db, pk)
|
||||
if not menu:
|
||||
@@ -61,8 +60,7 @@ class MenuService:
|
||||
parent_menu = await MenuDao.get(db, obj.parent_id)
|
||||
if not parent_menu:
|
||||
raise errors.NotFoundError(msg='父级菜单不存在')
|
||||
new_obj = obj.dict()
|
||||
count = await MenuDao.update(db, pk, new_obj, user_id)
|
||||
count = await MenuDao.update(db, pk, obj)
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -24,7 +24,7 @@ class RoleService:
|
||||
return await RoleDao.get_all(name=name, data_scope=data_scope)
|
||||
|
||||
@staticmethod
|
||||
async def create(*, obj: CreateRole, user_id: int) -> None:
|
||||
async def create(*, obj: CreateRole) -> None:
|
||||
async with async_db_session.begin() as db:
|
||||
role = await RoleDao.get_by_name(db, obj.name)
|
||||
if role:
|
||||
@@ -33,10 +33,10 @@ class RoleService:
|
||||
menu = await MenuDao.get(db, menu_id)
|
||||
if not menu:
|
||||
raise errors.ForbiddenError(msg='菜单不存在')
|
||||
await RoleDao.create(db, obj, user_id)
|
||||
await RoleDao.create(db, obj)
|
||||
|
||||
@staticmethod
|
||||
async def update(*, pk: int, obj: UpdateRole, user_id: int) -> int:
|
||||
async def update(*, pk: int, obj: UpdateRole) -> int:
|
||||
async with async_db_session.begin() as db:
|
||||
role = await RoleDao.get(db, pk)
|
||||
if not role:
|
||||
@@ -49,7 +49,7 @@ class RoleService:
|
||||
menu = await MenuDao.get(db, menu_id)
|
||||
if not menu:
|
||||
raise errors.ForbiddenError(msg='菜单不存在')
|
||||
count = await RoleDao.update(db, pk, obj, user_id)
|
||||
count = await RoleDao.update(db, pk, obj)
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
||||
from backend.app.core.conf import settings
|
||||
from backend.app.database.base_class import MappedBase
|
||||
from backend.app.models.base import MappedBase
|
||||
from backend.app.database.db_mysql import create_engine_and_session
|
||||
|
||||
TEST_DB_DATABASE = settings.DB_DATABASE + '_test'
|
||||
@@ -19,11 +17,7 @@ async_engine, async_db_session = create_engine_and_session(SQLALCHEMY_DATABASE_U
|
||||
|
||||
|
||||
async def override_get_db() -> AsyncSession:
|
||||
"""
|
||||
session 生成器
|
||||
|
||||
:return:
|
||||
"""
|
||||
"""session 生成器"""
|
||||
session = async_db_session()
|
||||
try:
|
||||
yield session
|
||||
@@ -35,8 +29,6 @@ async def override_get_db() -> AsyncSession:
|
||||
|
||||
|
||||
async def create_table():
|
||||
"""
|
||||
创建数据库表
|
||||
"""
|
||||
"""创建数据库表"""
|
||||
async with async_engine.begin() as coon:
|
||||
await coon.run_sync(MappedBase.metadata.create_all)
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
def get_uuid_str() -> str:
|
||||
"""
|
||||
生成uuid
|
||||
|
||||
:return: str(uuid)
|
||||
"""
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def get_current_timestamp() -> float:
|
||||
"""
|
||||
生成当前时间戳
|
||||
|
||||
:return:
|
||||
"""
|
||||
return datetime.datetime.now().timestamp()
|
||||
@@ -1,224 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
from backend.app.core.conf import settings
|
||||
|
||||
|
||||
class DateTimeUtils:
|
||||
def __init__(self, timezone_str=settings.DATETIME_TIMEZONE):
|
||||
"""
|
||||
初始化函数,设置时区
|
||||
|
||||
:param timezone_str: 时区字符串,默认为 UTC
|
||||
"""
|
||||
self.timezone_str = timezone_str
|
||||
self.timezone = pytz.timezone(self.timezone_str)
|
||||
|
||||
def get_current_time(self) -> datetime.datetime:
|
||||
"""
|
||||
获取当前时间
|
||||
|
||||
:return: 当前时间的 datetime 对象
|
||||
"""
|
||||
return datetime.datetime.now(self.timezone)
|
||||
|
||||
@staticmethod
|
||||
def get_current_timestamp() -> int:
|
||||
"""
|
||||
获取当前时间戳 (秒)
|
||||
|
||||
:return: 当前时间戳 (秒)
|
||||
"""
|
||||
return int(datetime.datetime.now().timestamp())
|
||||
|
||||
@staticmethod
|
||||
def get_current_milliseconds() -> int:
|
||||
"""
|
||||
获取当前时间戳 (毫秒)
|
||||
|
||||
:return: 当前时间戳 (毫秒)
|
||||
"""
|
||||
return int(datetime.datetime.now().timestamp() * 1000)
|
||||
|
||||
def timestamp_to_datetime(self, timestamp: int) -> datetime.datetime:
|
||||
"""
|
||||
时间戳转 datetime 对象
|
||||
|
||||
:param timestamp: 时间戳 (秒)
|
||||
:return: datetime 对象
|
||||
"""
|
||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=self.timezone)
|
||||
|
||||
def datetime_to_timestamp(self, dt: datetime.datetime) -> int:
|
||||
"""
|
||||
datetime 对象转时间戳(秒)
|
||||
|
||||
:param dt: datetime 对象
|
||||
:return: 时间戳 (秒)
|
||||
"""
|
||||
return int(dt.astimezone(self.timezone).timestamp())
|
||||
|
||||
def datetime_to_milliseconds(self, dt: datetime.datetime) -> int:
|
||||
"""
|
||||
datetime 对象转时间戳(毫秒)
|
||||
|
||||
:param dt: datetime 对象
|
||||
:return: 时间戳 (毫秒)
|
||||
"""
|
||||
return int(dt.astimezone(self.timezone).timestamp() * 1000)
|
||||
|
||||
def str_to_datetime(self, time_str: str, format_str: str = settings.DATETIME_FORMAT) -> datetime.datetime:
|
||||
"""
|
||||
时间字符串转 datetime 对象
|
||||
|
||||
:param time_str: 时间字符串
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: datetime 对象
|
||||
"""
|
||||
return datetime.datetime.strptime(time_str, format_str).replace(tzinfo=self.timezone)
|
||||
|
||||
def datetime_to_str(self, dt: datetime.datetime, format_str: str = settings.DATETIME_FORMAT) -> str:
|
||||
"""
|
||||
datetime 对象转时间字符串
|
||||
|
||||
:param dt: datetime 对象
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: 时间字符串
|
||||
"""
|
||||
return dt.astimezone(self.timezone).strftime(format_str)
|
||||
|
||||
@staticmethod
|
||||
def get_timezone(timezone_str: str) -> pytz.timezone:
|
||||
"""
|
||||
获取指定时区的 pytz.timezone 对象
|
||||
|
||||
:param timezone_str: 时区字符串
|
||||
:return: pytz.timezone 对象
|
||||
"""
|
||||
return pytz.timezone(timezone_str)
|
||||
|
||||
def get_timezone_time(self, timezone_str: str) -> datetime.datetime:
|
||||
"""
|
||||
获取指定时区的当前时间
|
||||
|
||||
:param timezone_str: 时区字符串
|
||||
:return: 当前时间的 datetime 对象
|
||||
"""
|
||||
timezone = self.get_timezone(timezone_str)
|
||||
return datetime.datetime.now(timezone)
|
||||
|
||||
def datetime_to_timezone(self, dt: datetime.datetime, timezone_str: str) -> datetime.datetime:
|
||||
"""
|
||||
将 datetime 对象转换为指定时区的 datetime 对象
|
||||
|
||||
:param dt: datetime 对象
|
||||
:param timezone_str: 目标时区字符串
|
||||
:return: 目标时区的 datetime 对象
|
||||
"""
|
||||
timezone = self.get_timezone(timezone_str)
|
||||
return dt.astimezone(timezone)
|
||||
|
||||
def datetime_to_timezone_str(
|
||||
self, dt: datetime.datetime, timezone_str: str, format_str: str = settings.DATETIME_FORMAT
|
||||
) -> str:
|
||||
"""
|
||||
将 datetime 对象转换为指定时区的时间字符串
|
||||
|
||||
:param dt: datetime 对象
|
||||
:param timezone_str: 目标时区字符串
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: 目标时区的时间字符串
|
||||
"""
|
||||
dt_timezone = self.datetime_to_timezone(dt, timezone_str)
|
||||
return dt_timezone.strftime(format_str)
|
||||
|
||||
def str_to_timezone(
|
||||
self, time_str: str, timezone_str: str, format_str: str = settings.DATETIME_FORMAT
|
||||
) -> datetime.datetime:
|
||||
"""
|
||||
将指定时区的时间字符串转换为 datetime 对象
|
||||
|
||||
:param time_str: 指定时区的时间字符串
|
||||
:param timezone_str: 指定时区字符串
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: datetime 对象
|
||||
"""
|
||||
dt = datetime.datetime.strptime(time_str, format_str).replace(tzinfo=self.timezone)
|
||||
return self.datetime_to_timezone(dt, timezone_str)
|
||||
|
||||
@staticmethod
|
||||
def datetime_to_utc(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""
|
||||
将 datetime 对象转换为 UTC 时间
|
||||
|
||||
:param dt: datetime 对象
|
||||
:return: UTC 时间的 datetime 对象
|
||||
"""
|
||||
return dt.astimezone(pytz.utc)
|
||||
|
||||
def str_to_utc(self, time_str: str, format_str: str = settings.DATETIME_FORMAT) -> datetime.datetime:
|
||||
"""
|
||||
将时间字符串转换为 UTC 时间的 datetime 对象
|
||||
|
||||
:param time_str: 时间字符串
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: UTC 时间的 datetime 对象
|
||||
"""
|
||||
dt = datetime.datetime.strptime(time_str, format_str).replace(tzinfo=self.timezone)
|
||||
return self.datetime_to_utc(dt)
|
||||
|
||||
def utc_to_datetime(self, utc_time: datetime.datetime) -> datetime.datetime:
|
||||
"""
|
||||
将 UTC 时间的 datetime 对象转换为指定时区的 datetime 对象
|
||||
|
||||
:param utc_time: UTC 时间的 datetime 对象
|
||||
:return: 目标时区的 datetime 对象
|
||||
"""
|
||||
return utc_time.replace(tzinfo=pytz.utc).astimezone(self.timezone).replace(tzinfo=None)
|
||||
|
||||
def get_expire_time(self, expires_delta: datetime.timedelta) -> datetime:
|
||||
"""
|
||||
获取过期时间
|
||||
|
||||
:param expires_delta: 时间间隔对象
|
||||
:return: 过期时间的 datetime 对象
|
||||
"""
|
||||
return self.get_current_time() + expires_delta
|
||||
|
||||
@staticmethod
|
||||
def get_expire_time_from_datetime(expire_time: datetime, seconds: int) -> datetime:
|
||||
"""
|
||||
获取从指定时间开始一定时间后的过期时间
|
||||
|
||||
:param expire_time: 指定时间的 datetime 对象
|
||||
:param seconds: 时间间隔(秒)
|
||||
:return: 过期时间的 datetime 对象
|
||||
"""
|
||||
return expire_time + datetime.timedelta(seconds=seconds)
|
||||
|
||||
@staticmethod
|
||||
def get_expire_seconds(expires_delta: datetime.timedelta) -> int:
|
||||
"""
|
||||
获取过期时间(秒)
|
||||
|
||||
:param expires_delta: 时间间隔对象
|
||||
:return: 过期时间(秒)
|
||||
"""
|
||||
return int(expires_delta.total_seconds())
|
||||
|
||||
def get_expire_seconds_from_datetime(self, expire_datetime: datetime) -> int:
|
||||
"""
|
||||
获取从指定时间开始到当前时间的时间间隔(秒)
|
||||
|
||||
:param expire_datetime: 指定时间的 datetime 对象
|
||||
:return: 时间间隔(秒)
|
||||
"""
|
||||
current_time = self.get_current_time()
|
||||
if expire_datetime < current_time:
|
||||
return 0
|
||||
return int((expire_datetime - current_time).total_seconds())
|
||||
|
||||
|
||||
datetime_utils = DateTimeUtils()
|
||||
@@ -2,12 +2,12 @@ import os
|
||||
import platform
|
||||
import socket
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from typing import List
|
||||
|
||||
import psutil
|
||||
|
||||
from backend.app.core.conf import settings
|
||||
from backend.app.utils.timezone import timezone_utils
|
||||
|
||||
|
||||
class ServerInfo:
|
||||
@@ -23,16 +23,24 @@ class ServerInfo:
|
||||
|
||||
@staticmethod
|
||||
def fmt_timedelta(td: timedelta) -> str:
|
||||
"""格式化时间戳"""
|
||||
days, rem = divmod(td.seconds, 86400)
|
||||
"""格式化时间差"""
|
||||
total_seconds = round(td.total_seconds())
|
||||
days, rem = divmod(total_seconds, 86400)
|
||||
hours, rem = divmod(rem, 3600)
|
||||
minutes, _ = divmod(rem, 60)
|
||||
res = f'{minutes} 分钟'
|
||||
if hours:
|
||||
res = f'{hours} 小时 {res}'
|
||||
minutes, seconds = divmod(rem, 60)
|
||||
parts = []
|
||||
if days:
|
||||
res = f'{days} 天 {res}'
|
||||
return res
|
||||
parts.append('{} 天'.format(days))
|
||||
if hours:
|
||||
parts.append('{} 小时'.format(hours))
|
||||
if minutes:
|
||||
parts.append('{} 分钟'.format(minutes))
|
||||
if seconds:
|
||||
parts.append('{} 秒'.format(seconds))
|
||||
if len(parts) == 0:
|
||||
return '0 秒'
|
||||
else:
|
||||
return ' '.join(parts)
|
||||
|
||||
@staticmethod
|
||||
def get_cpu_info() -> dict:
|
||||
@@ -96,7 +104,7 @@ class ServerInfo:
|
||||
"""获取服务信息"""
|
||||
process = psutil.Process(os.getpid())
|
||||
mem_info = process.memory_info()
|
||||
start_time = datetime.fromtimestamp(process.create_time())
|
||||
start_time = timezone_utils.utc_timestamp_to_timezone_datetime(process.create_time())
|
||||
return {
|
||||
'name': 'Python3',
|
||||
'version': platform.python_version(),
|
||||
@@ -105,6 +113,6 @@ class ServerInfo:
|
||||
'mem_vms': ServerInfo.format_bytes(mem_info.vms), # 虚拟内存, 即当前进程申请的虚拟内存
|
||||
'mem_rss': ServerInfo.format_bytes(mem_info.rss), # 常驻内存, 即当前进程实际使用的物理内存
|
||||
'mem_free': ServerInfo.format_bytes(mem_info.vms - mem_info.rss), # 空闲内存
|
||||
'startup': start_time.strftime(settings.DATETIME_FORMAT),
|
||||
'elapsed': f'{ServerInfo.fmt_timedelta(datetime.now() - start_time)}',
|
||||
'startup': start_time,
|
||||
'elapsed': f'{ServerInfo.fmt_timedelta(timezone_utils.get_timezone_datetime() - start_time)}',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
|
||||
import pytz
|
||||
|
||||
from backend.app.core.conf import settings
|
||||
|
||||
|
||||
class TimeZoneUtils:
|
||||
def __init__(self, timezone_str=settings.DATETIME_TIMEZONE):
|
||||
self.timezone = pytz.timezone(timezone_str)
|
||||
|
||||
def get_timezone_datetime(self) -> datetime.datetime:
|
||||
"""
|
||||
获取时区时间
|
||||
|
||||
:return:
|
||||
"""
|
||||
return datetime.datetime.now(self.timezone)
|
||||
|
||||
def get_timezone_timestamp(self) -> int:
|
||||
"""
|
||||
获取时区时间戳 (秒)
|
||||
|
||||
:return:
|
||||
"""
|
||||
return int(self.get_timezone_datetime().timestamp())
|
||||
|
||||
def get_timezone_milliseconds(self) -> int:
|
||||
"""
|
||||
获取时区时间戳 (毫秒)
|
||||
|
||||
:return:
|
||||
"""
|
||||
return int(self.get_timezone_datetime().timestamp() * 1000)
|
||||
|
||||
def datetime_to_timezone_str(self, dt: datetime.datetime, format_str: str = settings.DATETIME_FORMAT) -> str:
|
||||
"""
|
||||
datetime 对象转时区时间字符串
|
||||
|
||||
:param dt:
|
||||
:param format_str:
|
||||
:return:
|
||||
"""
|
||||
return dt.astimezone(self.timezone).strftime(format_str)
|
||||
|
||||
def datetime_to_timezone_datetime(self, dt: datetime.datetime) -> datetime.datetime:
|
||||
"""
|
||||
datetime 对象转 datetime 时区对象
|
||||
|
||||
:param dt:
|
||||
:return:
|
||||
"""
|
||||
return dt.astimezone(self.timezone)
|
||||
|
||||
@staticmethod
|
||||
def datetime_to_timezone_utc(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""
|
||||
datetime 对象转 datetime UTC 对象
|
||||
|
||||
:param dt:
|
||||
:return:
|
||||
"""
|
||||
return dt.astimezone(pytz.utc)
|
||||
|
||||
def datetime_to_timezone_timestamp(self, dt: datetime.datetime) -> int:
|
||||
"""
|
||||
datetime 对象转时区时间戳(秒)
|
||||
|
||||
:param dt:
|
||||
:return:
|
||||
"""
|
||||
return int(dt.astimezone(self.timezone).timestamp())
|
||||
|
||||
def datetime_to_timezone_milliseconds(self, dt: datetime.datetime) -> int:
|
||||
"""
|
||||
datetime 对象转时区时间戳(毫秒)
|
||||
|
||||
:param dt:
|
||||
:return:
|
||||
"""
|
||||
return int(dt.astimezone(self.timezone).timestamp() * 1000)
|
||||
|
||||
def str_to_timezone_utc(self, time_str: str, format_str: str = settings.DATETIME_FORMAT) -> datetime.datetime:
|
||||
"""
|
||||
时间字符串转时区 datetime UTC 对象
|
||||
|
||||
:param time_str:
|
||||
:param format_str:
|
||||
:return:
|
||||
"""
|
||||
dt = datetime.datetime.strptime(time_str, format_str).replace(tzinfo=self.timezone)
|
||||
return self.datetime_to_timezone_utc(dt)
|
||||
|
||||
def str_to_timezone_datetime(self, time_str: str, format_str: str = settings.DATETIME_FORMAT) -> datetime.datetime:
|
||||
"""
|
||||
时间字符串转 datetime 时区对象
|
||||
|
||||
:param time_str:
|
||||
:param format_str:
|
||||
:return:
|
||||
"""
|
||||
return datetime.datetime.strptime(time_str, format_str).replace(tzinfo=self.timezone)
|
||||
|
||||
def utc_datetime_to_timezone_datetime(self, utc_time: datetime.datetime) -> datetime.datetime:
|
||||
"""
|
||||
datetime UTC 对象转 datetime 时区对象
|
||||
|
||||
:param utc_time:
|
||||
:return:
|
||||
"""
|
||||
return utc_time.replace(tzinfo=pytz.utc).astimezone(self.timezone)
|
||||
|
||||
def utc_timestamp_to_timezone_datetime(self, timestamp: int) -> datetime.datetime:
|
||||
"""
|
||||
时间戳转 datetime 时区对象
|
||||
|
||||
:param timestamp:
|
||||
:return:
|
||||
"""
|
||||
utc_datetime = datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=pytz.utc)
|
||||
return self.datetime_to_timezone_datetime(utc_datetime)
|
||||
|
||||
def get_timezone_expire_time(self, expires_delta: datetime.timedelta) -> datetime.datetime:
|
||||
"""
|
||||
获取时区过期时间
|
||||
|
||||
:param expires_delta:
|
||||
:return:
|
||||
"""
|
||||
return self.get_timezone_datetime() + expires_delta
|
||||
|
||||
def get_timezone_expire_seconds(self, expire_datetime: datetime.datetime) -> int:
|
||||
"""
|
||||
获取从指定时间开始到当前时间的时间间隔(秒)
|
||||
|
||||
:param expire_datetime: 指定时间的 datetime 对象
|
||||
:return: 时间间隔(秒)
|
||||
"""
|
||||
timezone_datetime = self.get_timezone_datetime()
|
||||
expire_datetime = self.datetime_to_timezone_datetime(expire_datetime)
|
||||
if expire_datetime < timezone_datetime:
|
||||
return 0
|
||||
return int((expire_datetime - timezone_datetime).total_seconds())
|
||||
|
||||
|
||||
timezone_utils = TimeZoneUtils()
|
||||
Reference in New Issue
Block a user