diff --git a/backend/app/admin/api/v1/sys/__init__.py b/backend/app/admin/api/v1/sys/__init__.py index fca09acc..3fc78ad5 100644 --- a/backend/app/admin/api/v1/sys/__init__.py +++ b/backend/app/admin/api/v1/sys/__init__.py @@ -5,6 +5,8 @@ from backend.app.admin.api.v1.sys.api import router as api_router from backend.app.admin.api.v1.sys.casbin import router as casbin_router from backend.app.admin.api.v1.sys.config import router as config_router +from backend.app.admin.api.v1.sys.data_rule import router as data_rule_router +from backend.app.admin.api.v1.sys.data_rule_type import router as data_rule_type_router from backend.app.admin.api.v1.sys.dept import router as dept_router from backend.app.admin.api.v1.sys.dict_data import router as dict_data_router from backend.app.admin.api.v1.sys.dict_type import router as dict_type_router @@ -23,3 +25,5 @@ router.include_router(menu_router, prefix='/menus', tags=['系统目录']) router.include_router(role_router, prefix='/roles', tags=['系统角色']) router.include_router(user_router, prefix='/users', tags=['系统用户']) +router.include_router(data_rule_router, prefix='/data-rules', tags=['系统数据权限规则']) +router.include_router(data_rule_type_router, prefix='/data-rule-types', tags=['系统数据权限类型']) diff --git a/backend/app/admin/api/v1/sys/api.py b/backend/app/admin/api/v1/sys/api.py index a37faffc..a8c855e1 100644 --- a/backend/app/admin/api/v1/sys/api.py +++ b/backend/app/admin/api/v1/sys/api.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Depends, Path, Query +from fastapi import APIRouter, Depends, Path, Query, Request from backend.app.admin.schema.api import CreateApiParam, GetApiListDetails, UpdateApiParam from backend.app.admin.service.api_service import api_service @@ -37,12 +37,13 @@ async def get_api(pk: Annotated[int, Path(...)]) -> ResponseModel: ], ) async def get_pagination_apis( + request: Request, db: CurrentSession, name: Annotated[str | None, Query()] = None, method: Annotated[str | None, Query()] = None, path: Annotated[str | None, Query()] = None, ) -> ResponseModel: - api_select = await api_service.get_select(name=name, method=method, path=path) + api_select = await api_service.get_select(request=request, name=name, method=method, path=path) page_data = await paging_data(db, api_select, GetApiListDetails) return response_base.success(data=page_data) diff --git a/backend/app/admin/api/v1/sys/data_rule.py b/backend/app/admin/api/v1/sys/data_rule.py new file mode 100644 index 00000000..51715677 --- /dev/null +++ b/backend/app/admin/api/v1/sys/data_rule.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import Annotated + +from fastapi import APIRouter, Depends, Path, Query + +from backend.app.admin.schema.data_rule import CreateDataRuleParam, GetDataRuleListDetails, UpdateDataRuleParam +from backend.app.admin.service.data_rule_service import data_rule_service +from backend.common.pagination import DependsPagination, paging_data +from backend.common.response.response_schema import ResponseModel, response_base +from backend.common.security.jwt import DependsJwtAuth +from backend.common.security.permission import RequestPermission +from backend.common.security.rbac import DependsRBAC +from backend.database.db_mysql import CurrentSession +from backend.utils.serializers import select_as_dict + +router = APIRouter() + + +@router.get('/models', summary='获取支持过滤的数据库模型', dependencies=[DependsJwtAuth]) +async def get_data_rule_models() -> ResponseModel: + models = await data_rule_service.get_models() + return response_base.success(data=models) + + +@router.get('/model/{model}/columns', summary='获取支持过滤的数据库模型列', dependencies=[DependsJwtAuth]) +async def get_data_rule_model_columns(model: Annotated[str, Path()]) -> ResponseModel: + models = await data_rule_service.get_columns(model=model) + return response_base.success(data=models) + + +@router.get('/{pk}', summary='获取数据权限规则详情', dependencies=[DependsJwtAuth]) +async def get_data_rule(pk: Annotated[int, Path(...)]) -> ResponseModel: + data_rule = await data_rule_service.get(pk=pk) + data = GetDataRuleListDetails(**select_as_dict(data_rule)) + return response_base.success(data=data) + + +@router.get( + '', + summary='(模糊条件)分页获取所有数据权限规则', + dependencies=[ + DependsJwtAuth, + DependsPagination, + ], +) +async def get_pagination_data_rule(db: CurrentSession, name: Annotated[str | None, Query()] = None) -> ResponseModel: + data_rule_select = await data_rule_service.get_select(name=name) + page_data = await paging_data(db, data_rule_select, GetDataRuleListDetails) + return response_base.success(data=page_data) + + +@router.post( + '', + summary='创建数据权限规则', + dependencies=[ + Depends(RequestPermission('data:rule:add')), + DependsRBAC, + ], +) +async def create_data_rule(obj: CreateDataRuleParam) -> ResponseModel: + await data_rule_service.create(obj=obj) + return response_base.success() + + +@router.put( + '/{pk}', + summary='更新数据权限规则', + dependencies=[ + Depends(RequestPermission('data:rule:edit')), + DependsRBAC, + ], +) +async def update_data_rule(pk: Annotated[int, Path(...)], obj: UpdateDataRuleParam) -> ResponseModel: + count = await data_rule_service.update(pk=pk, obj=obj) + if count > 0: + return response_base.success() + return response_base.fail() + + +@router.delete( + '', + summary='(批量)删除数据权限规则', + dependencies=[ + Depends(RequestPermission('data:rule:del')), + DependsRBAC, + ], +) +async def delete_data_rule(pk: Annotated[list[int], Query(...)]) -> ResponseModel: + count = await data_rule_service.delete(pk=pk) + if count > 0: + return response_base.success() + return response_base.fail() diff --git a/backend/app/admin/api/v1/sys/data_rule_type.py b/backend/app/admin/api/v1/sys/data_rule_type.py new file mode 100644 index 00000000..deae9b42 --- /dev/null +++ b/backend/app/admin/api/v1/sys/data_rule_type.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import Annotated + +from fastapi import APIRouter, Depends, Path, Query + +from backend.app.admin.schema.data_rule_type import ( + CreateDataRuleTypeParam, + GetDataRuleTypeListDetails, + UpdateDataRuleTypeParam, +) +from backend.app.admin.service.data_rule_type_service import data_rule_type_service +from backend.common.pagination import DependsPagination, paging_data +from backend.common.response.response_schema import ResponseModel, response_base +from backend.common.security.jwt import DependsJwtAuth +from backend.common.security.permission import RequestPermission +from backend.common.security.rbac import DependsRBAC +from backend.database.db_mysql import CurrentSession +from backend.utils.serializers import select_as_dict + +router = APIRouter() + + +@router.get('/{pk}', summary='获取数据权限规则类型详情', dependencies=[DependsJwtAuth]) +async def get_data_rule_type(pk: Annotated[int, Path(...)]) -> ResponseModel: + data_rule_type = await data_rule_type_service.get(pk=pk) + data = GetDataRuleTypeListDetails(**select_as_dict(data_rule_type)) + return response_base.success(data=data) + + +@router.get( + '', + summary='(模糊条件)分页获取所有数据权限规则类型', + dependencies=[ + DependsJwtAuth, + DependsPagination, + ], +) +async def get_pagination_data_rule_type(db: CurrentSession) -> ResponseModel: + data_rule_type_select = await data_rule_type_service.get_select() + page_data = await paging_data(db, data_rule_type_select, GetDataRuleTypeListDetails) + return response_base.success(data=page_data) + + +@router.post( + '', + summary='创建数据权限规则类型', + dependencies=[ + Depends(RequestPermission('data:rule:type:add')), + DependsRBAC, + ], +) +async def create_data_rule_type(obj: CreateDataRuleTypeParam) -> ResponseModel: + await data_rule_type_service.create(obj=obj) + return response_base.success() + + +@router.put( + '/{pk}', + summary='更新数据权限规则类型', + dependencies=[ + Depends(RequestPermission('data:rule:type:edit')), + DependsRBAC, + ], +) +async def update_data_rule_type(pk: Annotated[int, Path(...)], obj: UpdateDataRuleTypeParam) -> ResponseModel: + count = await data_rule_type_service.update(pk=pk, obj=obj) + if count > 0: + return response_base.success() + return response_base.fail() + + +@router.delete( + '', + summary='(批量)删除数据权限规则类型', + dependencies=[ + Depends(RequestPermission('data:rule:type:del')), + DependsRBAC, + ], +) +async def delete_data_rule_type(pk: Annotated[list[int], Query(...)]) -> ResponseModel: + count = await data_rule_type_service.delete(pk=pk) + if count > 0: + return response_base.success() + return response_base.fail() diff --git a/backend/app/admin/api/v1/sys/role.py b/backend/app/admin/api/v1/sys/role.py index e08d4edf..7b05fec9 100644 --- a/backend/app/admin/api/v1/sys/role.py +++ b/backend/app/admin/api/v1/sys/role.py @@ -4,7 +4,13 @@ from fastapi import APIRouter, Depends, Path, Query, Request -from backend.app.admin.schema.role import CreateRoleParam, GetRoleListDetails, UpdateRoleMenuParam, UpdateRoleParam +from backend.app.admin.schema.role import ( + CreateRoleParam, + GetRoleListDetails, + UpdateRoleMenuParam, + UpdateRoleParam, + UpdateRoleRuleParam, +) from backend.app.admin.service.menu_service import menu_service from backend.app.admin.service.role_service import role_service from backend.common.pagination import DependsPagination, paging_data @@ -27,7 +33,7 @@ async def get_all_roles() -> ResponseModel: @router.get('/{pk}/all', summary='获取用户所有角色', dependencies=[DependsJwtAuth]) async def get_user_all_roles(pk: Annotated[int, Path(...)]) -> ResponseModel: - roles = await role_service.get_user_roles(pk=pk) + roles = await role_service.get_by_user(pk=pk) data = select_list_serialize(roles) return response_base.success(data=data) @@ -109,6 +115,23 @@ async def update_role_menus( return response_base.fail() +@router.put( + '/{pk}/rule', + summary='更新角色数据权限规则', + dependencies=[ + Depends(RequestPermission('sys:role:rule:edit')), + DependsRBAC, + ], +) +async def update_role_rules( + request: Request, pk: Annotated[int, Path(...)], rule_ids: UpdateRoleRuleParam +) -> ResponseModel: + count = await role_service.update_role_rule(request=request, pk=pk, rule_ids=rule_ids) + if count > 0: + return response_base.success() + return response_base.fail() + + @router.delete( '', summary='(批量)删除角色', diff --git a/backend/app/admin/crud/crud_api.py b/backend/app/admin/crud/crud_api.py index a0a4776d..12050253 100644 --- a/backend/app/admin/crud/crud_api.py +++ b/backend/app/admin/crud/crud_api.py @@ -2,12 +2,14 @@ # -*- coding: utf-8 -*- from typing import Sequence +from fastapi import Request from sqlalchemy import Select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy_crud_plus import CRUDPlus from backend.app.admin.model import Api from backend.app.admin.schema.api import CreateApiParam, UpdateApiParam +from backend.common.security.permission import filter_data_permission class CRUDApi(CRUDPlus[Api]): @@ -21,10 +23,11 @@ async def get(self, db: AsyncSession, pk: int) -> Api | None: """ return await self.select_model(db, pk) - async def get_list(self, name: str = None, method: str = None, path: str = None) -> Select: + async def get_list(self, request: Request, name: str = None, method: str = None, path: str = None) -> Select: """ 获取 API 列表 + :param request: :param name: :param method: :param path: @@ -37,7 +40,8 @@ async def get_list(self, name: str = None, method: str = None, path: str = None) filters.update(method=method) if path is not None: filters.update(path__like=f'%{path}%') - return await self.select_order('created_time', 'desc', **filters) + stmt = await self.select_order('created_time', 'desc', **filters) + return stmt.where(filter_data_permission(request)) async def get_all(self, db: AsyncSession) -> Sequence[Api]: """ diff --git a/backend/app/admin/crud/crud_data_rule.py b/backend/app/admin/crud/crud_data_rule.py new file mode 100644 index 00000000..8ef9e489 --- /dev/null +++ b/backend/app/admin/crud/crud_data_rule.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import Sequence + +from sqlalchemy import Select, desc, select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy_crud_plus import CRUDPlus + +from backend.app.admin.model import DataRule +from backend.app.admin.schema.data_rule import CreateDataRuleParam, UpdateDataRuleParam + + +class CRUDDataRule(CRUDPlus[DataRule]): + async def get(self, db: AsyncSession, pk: int) -> DataRule | None: + """ + 获取数据权限规则 + + :param db: + :param pk: + :return: + """ + return await self.select_model(db, pk) + + async def get_list(self, name: str = None) -> Select: + """ + 获取数据权限规则列表 + + :return: + """ + stmt = select(self.model).order_by(desc(self.model.created_time)) + where_list = [] + if name is not None: + where_list.append(self.model.name.like(f'%{name}%')) + if where_list: + stmt = stmt.where(*where_list) + return stmt + + async def get_by_name(self, db: AsyncSession, name: str): + """ + 通过 name 获取数据权限规则 + + :param db: + :param name: + :return: + """ + return await self.select_model_by_column(db, name=name) + + async def get_all(self, db: AsyncSession) -> Sequence[DataRule]: + """ + 获取所有数据权限规则 + + :param db: + :return: + """ + return await self.select_models(db) + + async def create(self, db: AsyncSession, obj_in: CreateDataRuleParam) -> None: + """ + 创建数据权限规则 + + :param db: + :param obj_in: + :return: + """ + await self.create_model(db, obj_in) + + async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDataRuleParam) -> int: + """ + 更新数据权限规则 + + :param db: + :param pk: + :param obj_in: + :return: + """ + return await self.update_model(db, pk, obj_in) + + async def delete(self, db: AsyncSession, pk: list[int]) -> int: + """ + 删除数据权限规则 + + :param db: + :param pk: + :return: + """ + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + + +data_rule_dao: CRUDDataRule = CRUDDataRule(DataRule) diff --git a/backend/app/admin/crud/crud_data_rule_type.py b/backend/app/admin/crud/crud_data_rule_type.py new file mode 100644 index 00000000..9ec3347a --- /dev/null +++ b/backend/app/admin/crud/crud_data_rule_type.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import Sequence + +from sqlalchemy import Select +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy_crud_plus import CRUDPlus + +from backend.app.admin.model import DataRuleType +from backend.app.admin.schema.data_rule_type import CreateDataRuleTypeParam, UpdateDataRuleTypeParam + + +class CRUDDataRuleType(CRUDPlus[DataRuleType]): + async def get(self, db: AsyncSession, pk: int) -> DataRuleType | None: + """ + 获取数据权限规则类型 + + :param db: + :param pk: + :return: + """ + return await self.select_model(db, pk) + + async def get_list(self) -> Select: + """ + 获取数据权限规则类型列表 + + :return: + """ + return await self.select_order('created_time', 'desc') + + async def get_by_name(self, db: AsyncSession, name: str) -> DataRuleType | None: + """ + 通过 name 获取数据权限规则类型 + + :param db: + :param name: + :return: + """ + return await self.select_model_by_column(db, name=name) + + async def get_all(self, db: AsyncSession) -> Sequence[DataRuleType]: + """ + 获取所有数据权限规则类型 + + :param db: + :return: + """ + return await self.select_models(db) + + async def create(self, db: AsyncSession, obj_in: CreateDataRuleTypeParam) -> None: + """ + 创建数据权限规则类型 + + :param db: + :param obj_in: + :return: + """ + await self.create_model(db, obj_in) + + async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDataRuleTypeParam) -> int: + """ + 更新数据权限规则类型 + + :param db: + :param pk: + :param obj_in: + :return: + """ + return await self.update_model(db, pk, obj_in) + + async def delete(self, db: AsyncSession, pk: list[int]) -> int: + """ + 删除数据权限规则类型 + + :param db: + :param pk: + :return: + """ + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + + +data_rule_type_dao: CRUDDataRuleType = CRUDDataRuleType(DataRuleType) diff --git a/backend/app/admin/crud/crud_role.py b/backend/app/admin/crud/crud_role.py index f40e37e5..8bfb6a0d 100644 --- a/backend/app/admin/crud/crud_role.py +++ b/backend/app/admin/crud/crud_role.py @@ -6,8 +6,13 @@ from sqlalchemy.orm import selectinload from sqlalchemy_crud_plus import CRUDPlus -from backend.app.admin.model import Menu, Role, User -from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam +from backend.app.admin.model import DataRule, Menu, Role, User +from backend.app.admin.schema.role import ( + CreateRoleParam, + UpdateRoleMenuParam, + UpdateRoleParam, + UpdateRoleRuleParam, +) class CRUDRole(CRUDPlus[Role]): @@ -29,7 +34,11 @@ async def get_with_relation(self, db, role_id: int) -> Role | None: :param role_id: :return: """ - stmt = select(self.model).options(selectinload(self.model.menus)).where(self.model.id == role_id) + stmt = ( + select(self.model) + .options(selectinload(self.model.menus), selectinload(self.model.rules)) + .where(self.model.id == role_id) + ) role = await db.execute(stmt) return role.scalars().first() @@ -42,7 +51,7 @@ async def get_all(self, db) -> Sequence[Role]: """ return await self.select_models(db) - async def get_user_roles(self, db, user_id: int) -> Sequence[Role]: + async def get_by_user(self, db, user_id: int) -> Sequence[Role]: """ 获取用户所有角色 @@ -122,6 +131,22 @@ async def update_menus(self, db, role_id: int, menu_ids: UpdateRoleMenuParam) -> current_role.menus = menus.scalars().all() return len(current_role.menus) + async def update_rules(self, db, role_id: int, rule_ids: UpdateRoleRuleParam) -> int: + """ + 更新角色数据权限 + + :param db: + :param role_id: + :param rule_ids: + :return: + """ + current_role = await self.get_with_relation(db, role_id) + # 更新数据权限 + stmt = select(DataRule).where(DataRule.id.in_(rule_ids.rules)) + rules = await db.execute(stmt) + current_role.rules = rules.scalars().all() + return len(current_role.rules) + async def delete(self, db, role_id: list[int]) -> int: """ 删除角色 diff --git a/backend/app/admin/crud/crud_user.py b/backend/app/admin/crud/crud_user.py index ee1c31d5..2bfd2b28 100644 --- a/backend/app/admin/crud/crud_user.py +++ b/backend/app/admin/crud/crud_user.py @@ -184,8 +184,10 @@ async def get_list(self, dept: int = None, username: str = None, phone: str = No """ stmt = ( select(self.model) - .options(selectinload(self.model.dept)) - .options(selectinload(self.model.roles).selectinload(Role.menus)) + .options( + selectinload(self.model.dept), + selectinload(self.model.roles).selectinload(Role.menus), + ) .order_by(desc(self.model.join_time)) ) where_list = [] @@ -291,17 +293,19 @@ async def set_multi_login(self, db: AsyncSession, user_id: int, multi_login: boo async def get_with_relation(self, db: AsyncSession, *, user_id: int = None, username: str = None) -> User | None: """ - 获取用户和(部门,角色,菜单) + 获取用户和(部门,角色,菜单,规则) :param db: :param user_id: :param username: :return: """ - stmt = ( - select(self.model) - .options(selectinload(self.model.dept)) - .options(selectinload(self.model.roles).joinedload(Role.menus)) + stmt = select(self.model).options( + selectinload(self.model.dept), + selectinload(self.model.roles).options( + selectinload(Role.menus), + selectinload(Role.rules), + ), ) filters = [] if user_id: diff --git a/backend/app/admin/model/__init__.py b/backend/app/admin/model/__init__.py index 345990fd..f36cca25 100644 --- a/backend/app/admin/model/__init__.py +++ b/backend/app/admin/model/__init__.py @@ -1,14 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from backend.app.admin.model.sys_api import Api -from backend.app.admin.model.sys_casbin_rule import CasbinRule -from backend.app.admin.model.sys_config import Config -from backend.app.admin.model.sys_dept import Dept -from backend.app.admin.model.sys_dict_data import DictData -from backend.app.admin.model.sys_dict_type import DictType -from backend.app.admin.model.sys_login_log import LoginLog -from backend.app.admin.model.sys_menu import Menu -from backend.app.admin.model.sys_opera_log import OperaLog -from backend.app.admin.model.sys_role import Role -from backend.app.admin.model.sys_user import User -from backend.app.admin.model.sys_user_social import UserSocial +from backend.app.admin.model.api import Api +from backend.app.admin.model.casbin_rule import CasbinRule +from backend.app.admin.model.config import Config +from backend.app.admin.model.data_rule import DataRule +from backend.app.admin.model.data_rule_type import DataRuleType +from backend.app.admin.model.dept import Dept +from backend.app.admin.model.dict_data import DictData +from backend.app.admin.model.dict_type import DictType +from backend.app.admin.model.login_log import LoginLog +from backend.app.admin.model.menu import Menu +from backend.app.admin.model.opera_log import OperaLog +from backend.app.admin.model.role import Role +from backend.app.admin.model.user import User +from backend.app.admin.model.user_social import UserSocial diff --git a/backend/app/admin/model/sys_api.py b/backend/app/admin/model/api.py similarity index 100% rename from backend/app/admin/model/sys_api.py rename to backend/app/admin/model/api.py diff --git a/backend/app/admin/model/sys_casbin_rule.py b/backend/app/admin/model/casbin_rule.py similarity index 100% rename from backend/app/admin/model/sys_casbin_rule.py rename to backend/app/admin/model/casbin_rule.py diff --git a/backend/app/admin/model/sys_config.py b/backend/app/admin/model/config.py similarity index 100% rename from backend/app/admin/model/sys_config.py rename to backend/app/admin/model/config.py diff --git a/backend/app/admin/model/data_rule.py b/backend/app/admin/model/data_rule.py new file mode 100644 index 00000000..e1cdb245 --- /dev/null +++ b/backend/app/admin/model/data_rule.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from sqlalchemy import ForeignKey, String +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from backend.app.admin.model.m2m import sys_role_data_rule +from backend.common.model import Base, id_key + + +class DataRule(Base): + """数据权限规则表""" + + __tablename__ = 'sys_data_rule' + + id: Mapped[id_key] = mapped_column(init=False) + name: Mapped[str] = mapped_column(String(255), unique=True, comment='规则名称') + model: Mapped[str] = mapped_column(String(50), comment='SQLA 模型类') + column: Mapped[str] = mapped_column(String(20), comment='数据库字段') + operator: Mapped[int] = mapped_column(comment='运算符(0:and、1:or)') + expression: Mapped[int] = mapped_column( + comment='表达式(0:>、1:>=、2:<、3:<=、4:==、5:!=、6:in、7:not_in)' + ) + value: Mapped[str] = mapped_column(String(255), comment='规则值') + + # 数据权限规则类型一对多 + type_id: Mapped[int] = mapped_column( + ForeignKey('sys_data_rule_type.id', ondelete='CASCADE'), comment='数据权限规则类型关联ID' + ) + type: Mapped['DataRuleType'] = relationship(init=False, back_populates='rules') # noqa: F821 + + # 角色规则多对多 + roles: Mapped[list['Role']] = relationship(init=False, secondary=sys_role_data_rule, back_populates='rules') # noqa: F821 diff --git a/backend/app/admin/model/data_rule_type.py b/backend/app/admin/model/data_rule_type.py new file mode 100644 index 00000000..4e686c3e --- /dev/null +++ b/backend/app/admin/model/data_rule_type.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from sqlalchemy import String +from sqlalchemy.dialects.mysql import LONGTEXT +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from backend.common.model import Base, id_key + + +class DataRuleType(Base): + """数据权限规则类型表""" + + __tablename__ = 'sys_data_rule_type' + + id: Mapped[id_key] = mapped_column(init=False) + name: Mapped[str] = mapped_column(String(255), unique=True, comment='规则类型名') + status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') + remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + + # 数据权限规则类型一对多 + rules: Mapped[list['DataRule']] = relationship(init=False, back_populates='type') # noqa: F821 diff --git a/backend/app/admin/model/sys_dept.py b/backend/app/admin/model/dept.py similarity index 99% rename from backend/app/admin/model/sys_dept.py rename to backend/app/admin/model/dept.py index 2e1492e9..a85805d9 100644 --- a/backend/app/admin/model/sys_dept.py +++ b/backend/app/admin/model/dept.py @@ -22,11 +22,13 @@ class Dept(Base): email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱') status: Mapped[int] = mapped_column(default=1, comment='部门状态(0停用 1正常)') del_flag: Mapped[bool] = mapped_column(default=False, comment='删除标志(0删除 1存在)') + # 父级部门一对多 parent_id: Mapped[int | None] = mapped_column( ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, index=True, comment='父部门ID' ) parent: Mapped[Union['Dept', None]] = relationship(init=False, back_populates='children', remote_side=[id]) children: Mapped[list['Dept'] | None] = relationship(init=False, back_populates='parent') + # 部门用户一对多 users: Mapped[list['User']] = relationship(init=False, back_populates='dept') # noqa: F821 diff --git a/backend/app/admin/model/sys_dict_data.py b/backend/app/admin/model/dict_data.py similarity index 85% rename from backend/app/admin/model/sys_dict_data.py rename to backend/app/admin/model/dict_data.py index 884bdc6b..4eac82b6 100644 --- a/backend/app/admin/model/sys_dict_data.py +++ b/backend/app/admin/model/dict_data.py @@ -18,6 +18,9 @@ class DictData(Base): sort: Mapped[int] = mapped_column(default=0, comment='排序') status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + # 字典类型一对多 - type_id: Mapped[int] = mapped_column(ForeignKey('sys_dict_type.id'), default=0, comment='字典类型关联ID') + type_id: Mapped[int] = mapped_column( + ForeignKey('sys_dict_type.id', ondelete='CASCADE'), default=0, comment='字典类型关联ID' + ) type: Mapped['DictType'] = relationship(init=False, back_populates='datas') # noqa: F821 diff --git a/backend/app/admin/model/sys_dict_type.py b/backend/app/admin/model/dict_type.py similarity index 99% rename from backend/app/admin/model/sys_dict_type.py rename to backend/app/admin/model/dict_type.py index 3190162e..c5676b4a 100644 --- a/backend/app/admin/model/sys_dict_type.py +++ b/backend/app/admin/model/dict_type.py @@ -17,5 +17,6 @@ class DictType(Base): code: Mapped[str] = mapped_column(String(32), unique=True, comment='字典类型编码') status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + # 字典类型一对多 datas: Mapped[list['DictData']] = relationship(init=False, back_populates='type') # noqa: F821 diff --git a/backend/app/admin/model/sys_login_log.py b/backend/app/admin/model/login_log.py similarity index 100% rename from backend/app/admin/model/sys_login_log.py rename to backend/app/admin/model/login_log.py diff --git a/backend/app/admin/model/m2m.py b/backend/app/admin/model/m2m.py new file mode 100644 index 00000000..acccd6f9 --- /dev/null +++ b/backend/app/admin/model/m2m.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from sqlalchemy import INT, Column, ForeignKey, Integer, Table + +from backend.common.model import MappedBase + +sys_user_role = Table( + 'sys_user_role', + MappedBase.metadata, + Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + Column('user_id', Integer, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'), + Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), +) + +sys_role_menu = Table( + 'sys_role_menu', + MappedBase.metadata, + Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), + Column('menu_id', Integer, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'), +) + +sys_role_data_rule = Table( + 'sys_role_data_rule', + MappedBase.metadata, + Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), + Column( + 'data_rule_id', + Integer, + ForeignKey('sys_data_rule.id', ondelete='CASCADE'), + primary_key=True, + comment='数据权限规则ID', + ), +) diff --git a/backend/app/admin/model/sys_menu.py b/backend/app/admin/model/menu.py similarity index 90% rename from backend/app/admin/model/sys_menu.py rename to backend/app/admin/model/menu.py index f3233b2c..8b75d29e 100644 --- a/backend/app/admin/model/sys_menu.py +++ b/backend/app/admin/model/menu.py @@ -6,7 +6,7 @@ from sqlalchemy.dialects.mysql import LONGTEXT from sqlalchemy.orm import Mapped, mapped_column, relationship -from backend.app.admin.model.sys_role_menu import sys_role_menu +from backend.app.admin.model.m2m import sys_role_menu from backend.common.model import Base, id_key @@ -29,13 +29,13 @@ class Menu(Base): show: Mapped[int] = mapped_column(default=1, comment='是否显示(0否 1是)') cache: Mapped[int] = mapped_column(default=1, comment='是否缓存(0否 1是)') remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + # 父级菜单一对多 parent_id: Mapped[int | None] = mapped_column( ForeignKey('sys_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID' ) parent: Mapped[Union['Menu', None]] = relationship(init=False, back_populates='children', remote_side=[id]) children: Mapped[list['Menu'] | None] = relationship(init=False, back_populates='parent') + # 菜单角色多对多 - roles: Mapped[list['Role']] = relationship( # noqa: F821 - init=False, secondary=sys_role_menu, back_populates='menus' - ) + roles: Mapped[list['Role']] = relationship(init=False, secondary=sys_role_menu, back_populates='menus') # noqa: F821 diff --git a/backend/app/admin/model/sys_opera_log.py b/backend/app/admin/model/opera_log.py similarity index 100% rename from backend/app/admin/model/sys_opera_log.py rename to backend/app/admin/model/opera_log.py diff --git a/backend/app/admin/model/role.py b/backend/app/admin/model/role.py new file mode 100644 index 00000000..108c85de --- /dev/null +++ b/backend/app/admin/model/role.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from sqlalchemy import String +from sqlalchemy.dialects.mysql import LONGTEXT +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from backend.app.admin.model.m2m import sys_role_data_rule, sys_role_menu, sys_user_role +from backend.common.model import Base, id_key + + +class Role(Base): + """角色表""" + + __tablename__ = 'sys_role' + + id: Mapped[id_key] = mapped_column(init=False) + name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称') + data_scope: Mapped[int | None] = mapped_column( + default=0, + comment='数据权限范围(0: 全部数据,1: 自定义数据,2: 所在部门及以下数据,3: 所在部门数据,4: 仅本人数据)', + ) + status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)') + remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') + + # 角色用户多对多 + users: Mapped[list['User']] = relationship(init=False, secondary=sys_user_role, back_populates='roles') # noqa: F821 + + # 角色菜单多对多 + menus: Mapped[list['Menu']] = relationship(init=False, secondary=sys_role_menu, back_populates='roles') # noqa: F821 + + # 角色数据权限规则多对多 + rules: Mapped[list['DataRule']] = relationship(init=False, secondary=sys_role_data_rule, back_populates='roles') # noqa: F821 diff --git a/backend/app/admin/model/sys_role.py b/backend/app/admin/model/sys_role.py deleted file mode 100644 index acf46c01..00000000 --- a/backend/app/admin/model/sys_role.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from sqlalchemy import String -from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.orm import Mapped, mapped_column, relationship - -from backend.app.admin.model.sys_role_menu import sys_role_menu -from backend.app.admin.model.sys_user_role import sys_user_role -from backend.common.model import Base, id_key - - -class Role(Base): - """角色表""" - - __tablename__ = 'sys_role' - - id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称') - data_scope: Mapped[int | None] = mapped_column(default=2, comment='权限范围(1:全部数据权限 2:自定义数据权限)') - status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)') - remark: Mapped[str | None] = mapped_column(LONGTEXT, default=None, comment='备注') - # 角色用户多对多 - users: Mapped[list['User']] = relationship( # noqa: F821 - init=False, secondary=sys_user_role, back_populates='roles' - ) - # 角色菜单多对多 - menus: Mapped[list['Menu']] = relationship( # noqa: F821 - init=False, secondary=sys_role_menu, back_populates='roles' - ) diff --git a/backend/app/admin/model/sys_role_menu.py b/backend/app/admin/model/sys_role_menu.py deleted file mode 100644 index e1f2cb7d..00000000 --- a/backend/app/admin/model/sys_role_menu.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from sqlalchemy import INT, Column, ForeignKey, Integer, Table - -from backend.common.model import MappedBase - -sys_role_menu = Table( - 'sys_role_menu', - MappedBase.metadata, - Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), - Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), - Column('menu_id', Integer, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'), -) diff --git a/backend/app/admin/model/sys_user_role.py b/backend/app/admin/model/sys_user_role.py deleted file mode 100644 index 1870360e..00000000 --- a/backend/app/admin/model/sys_user_role.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from sqlalchemy import INT, Column, ForeignKey, Integer, Table - -from backend.common.model import MappedBase - -sys_user_role = Table( - 'sys_user_role', - MappedBase.metadata, - Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), - Column('user_id', Integer, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'), - Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), -) diff --git a/backend/app/admin/model/sys_user.py b/backend/app/admin/model/user.py similarity index 90% rename from backend/app/admin/model/sys_user.py rename to backend/app/admin/model/user.py index 298cb128..16cbaab8 100644 --- a/backend/app/admin/model/sys_user.py +++ b/backend/app/admin/model/user.py @@ -6,7 +6,7 @@ from sqlalchemy import VARBINARY, ForeignKey, String from sqlalchemy.orm import Mapped, mapped_column, relationship -from backend.app.admin.model.sys_user_role import sys_user_role +from backend.app.admin.model.m2m import sys_user_role from backend.common.model import Base, id_key from backend.database.db_mysql import uuid4_str from backend.utils.timezone import timezone @@ -32,14 +32,15 @@ class User(Base): phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机号') join_time: Mapped[datetime] = mapped_column(init=False, default_factory=timezone.now, comment='注册时间') last_login_time: Mapped[datetime | None] = mapped_column(init=False, onupdate=timezone.now, comment='上次登录') + # 部门用户一对多 dept_id: Mapped[int | None] = mapped_column( ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, comment='部门关联ID' ) dept: Mapped[Union['Dept', None]] = relationship(init=False, back_populates='users') # noqa: F821 - # 用户角色多对多 - roles: Mapped[list['Role']] = relationship( # noqa: F821 - init=False, secondary=sys_user_role, back_populates='users' - ) - # 用户 OAuth2 一对多 + + # 用户社交信息一对多 socials: Mapped[list['UserSocial']] = relationship(init=False, back_populates='user') # noqa: F821 + + # 用户角色多对多 + roles: Mapped[list['Role']] = relationship(init=False, secondary=sys_user_role, back_populates='users') # noqa: F821 diff --git a/backend/app/admin/model/sys_user_social.py b/backend/app/admin/model/user_social.py similarity index 97% rename from backend/app/admin/model/sys_user_social.py rename to backend/app/admin/model/user_social.py index 42c8833a..50439aa6 100644 --- a/backend/app/admin/model/sys_user_social.py +++ b/backend/app/admin/model/user_social.py @@ -20,7 +20,8 @@ class UserSocial(Base): union_id: Mapped[str | None] = mapped_column(String(20), default=None, comment='第三方用户的 union id') scope: Mapped[str | None] = mapped_column(String(120), default=None, comment='第三方用户授予的权限') code: Mapped[str | None] = mapped_column(String(50), default=None, comment='用户的授权 code') - # 用户 OAuth2 一对多 + + # 用户社交信息一对多 user_id: Mapped[int | None] = mapped_column( ForeignKey('sys_user.id', ondelete='SET NULL'), default=None, comment='用户关联ID' ) diff --git a/backend/app/admin/schema/data_rule.py b/backend/app/admin/schema/data_rule.py new file mode 100644 index 00000000..8b3bb190 --- /dev/null +++ b/backend/app/admin/schema/data_rule.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from datetime import datetime + +from pydantic import ConfigDict, Field + +from backend.common.enums import RoleDataRuleExpressionType, RoleDataRuleOperatorType +from backend.common.schema import SchemaBase + + +class DataRuleSchemaBase(SchemaBase): + type_id: int + name: str + model: str + column: str + operator: RoleDataRuleOperatorType = Field(RoleDataRuleOperatorType.OR) + expression: RoleDataRuleExpressionType = Field(RoleDataRuleExpressionType.eq) + value: str + + +class CreateDataRuleParam(DataRuleSchemaBase): + pass + + +class UpdateDataRuleParam(DataRuleSchemaBase): + pass + + +class GetDataRuleListDetails(DataRuleSchemaBase): + model_config = ConfigDict(from_attributes=True) + + id: int + created_time: datetime + updated_time: datetime | None = None + + def __hash__(self): + return hash(self.name) diff --git a/backend/app/admin/schema/data_rule_type.py b/backend/app/admin/schema/data_rule_type.py new file mode 100644 index 00000000..ad2a05e7 --- /dev/null +++ b/backend/app/admin/schema/data_rule_type.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from datetime import datetime + +from pydantic import ConfigDict, Field + +from backend.common.enums import StatusType +from backend.common.schema import SchemaBase + + +class DataRuleTypeSchemaBase(SchemaBase): + name: str + status: StatusType = Field(default=StatusType.enable) + remark: str + + +class CreateDataRuleTypeParam(DataRuleTypeSchemaBase): + pass + + +class UpdateDataRuleTypeParam(DataRuleTypeSchemaBase): + pass + + +class GetDataRuleTypeListDetails(DataRuleTypeSchemaBase): + model_config = ConfigDict(from_attributes=True) + + id: int + created_time: datetime + updated_time: datetime | None = None diff --git a/backend/app/admin/schema/role.py b/backend/app/admin/schema/role.py index e1fd61ef..ef64cf44 100644 --- a/backend/app/admin/schema/role.py +++ b/backend/app/admin/schema/role.py @@ -4,16 +4,14 @@ from pydantic import ConfigDict, Field +from backend.app.admin.schema.data_rule import GetDataRuleListDetails from backend.app.admin.schema.menu import GetMenuListDetails -from backend.common.enums import RoleDataScopeType, StatusType +from backend.common.enums import StatusType from backend.common.schema import SchemaBase class RoleSchemaBase(SchemaBase): name: str - data_scope: RoleDataScopeType = Field( - default=RoleDataScopeType.custom, description='权限范围(1:全部数据权限 2:自定义数据权限)' - ) status: StatusType = Field(default=StatusType.enable) remark: str | None = None @@ -30,6 +28,10 @@ class UpdateRoleMenuParam(SchemaBase): menus: list[int] +class UpdateRoleRuleParam(SchemaBase): + rules: list[int] + + class GetRoleListDetails(RoleSchemaBase): model_config = ConfigDict(from_attributes=True) @@ -37,3 +39,4 @@ class GetRoleListDetails(RoleSchemaBase): created_time: datetime updated_time: datetime | None = None menus: list[GetMenuListDetails] + rules: list[GetDataRuleListDetails] diff --git a/backend/app/admin/service/api_service.py b/backend/app/admin/service/api_service.py index 1bddc4f7..bcd8b179 100644 --- a/backend/app/admin/service/api_service.py +++ b/backend/app/admin/service/api_service.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from typing import Sequence +from fastapi import Request from sqlalchemy import Select from backend.app.admin.crud.crud_api import api_dao @@ -21,8 +22,8 @@ async def get(*, pk: int) -> Api: return api @staticmethod - async def get_select(*, name: str = None, method: str = None, path: str = None) -> Select: - return await api_dao.get_list(name=name, method=method, path=path) + async def get_select(*, request: Request, name: str = None, method: str = None, path: str = None) -> Select: + return await api_dao.get_list(request=request, name=name, method=method, path=path) @staticmethod async def get_all() -> Sequence[Api]: diff --git a/backend/app/admin/service/data_rule_service.py b/backend/app/admin/service/data_rule_service.py new file mode 100644 index 00000000..bf1963e4 --- /dev/null +++ b/backend/app/admin/service/data_rule_service.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import Sequence + +from sqlalchemy import Select + +from backend.app.admin.crud.crud_data_rule import data_rule_dao +from backend.app.admin.crud.crud_data_rule_type import data_rule_type_dao +from backend.app.admin.model import DataRule +from backend.app.admin.schema.data_rule import CreateDataRuleParam, UpdateDataRuleParam +from backend.common.exception import errors +from backend.core.conf import settings +from backend.database.db_mysql import async_db_session +from backend.utils.import_parse import dynamic_import + + +class DataRuleService: + @staticmethod + async def get(*, pk: int) -> DataRule: + async with async_db_session() as db: + data_rule = await data_rule_dao.get(db, pk) + if not data_rule: + raise errors.NotFoundError(msg='数据规则不存在') + return data_rule + + @staticmethod + async def get_models() -> list: + return list(settings.DATA_PERMISSION_MODELS.keys()) + + @staticmethod + async def get_columns(model: str): + if model not in settings.DATA_PERMISSION_MODELS: + raise errors.NotFoundError(msg='数据模型不存在') + model_ins = dynamic_import(settings.DATA_PERMISSION_MODELS[model]) + model_columns = [ + key for key in model_ins.__table__.columns.keys() if key not in settings.DATA_PERMISSION_COLUMN_EXCLUDE + ] + return model_columns + + @staticmethod + async def get_select(*, name: str = None) -> Select: + return await data_rule_dao.get_list(name=name) + + @staticmethod + async def get_all() -> Sequence[DataRule]: + async with async_db_session() as db: + data_rules = await data_rule_dao.get_all(db) + return data_rules + + @staticmethod + async def create(*, obj: CreateDataRuleParam) -> None: + async with async_db_session.begin() as db: + data_rule = await data_rule_dao.get_by_name(db, obj.name) + if data_rule: + raise errors.ForbiddenError(msg='数据权限规则已存在') + data_rule_type = await data_rule_type_dao.get(db, obj.type_id) + if not data_rule_type: + raise errors.NotFoundError(msg='数据权限规则类型不存在') + await data_rule_dao.create(db, obj) + + @staticmethod + async def update(*, pk: int, obj: UpdateDataRuleParam) -> int: + async with async_db_session.begin() as db: + data_rule = await data_rule_dao.get(db, pk) + if not data_rule: + raise errors.NotFoundError(msg='数据权限规则不存在') + data_rule_type = await data_rule_type_dao.get(db, obj.type_id) + if not data_rule_type: + raise errors.NotFoundError(msg='数据权限规则类型不存在') + count = await data_rule_dao.update(db, pk, obj) + return count + + @staticmethod + async def delete(*, pk: list[int]) -> int: + async with async_db_session.begin() as db: + count = await data_rule_dao.delete(db, pk) + return count + + +data_rule_service: DataRuleService = DataRuleService() diff --git a/backend/app/admin/service/data_rule_type_service.py b/backend/app/admin/service/data_rule_type_service.py new file mode 100644 index 00000000..5bcf3b41 --- /dev/null +++ b/backend/app/admin/service/data_rule_type_service.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import Sequence + +from sqlalchemy import Select + +from backend.app.admin.crud.crud_data_rule_type import data_rule_type_dao +from backend.app.admin.model import DataRuleType +from backend.app.admin.schema.data_rule_type import CreateDataRuleTypeParam, UpdateDataRuleTypeParam +from backend.common.exception import errors +from backend.database.db_mysql import async_db_session + + +class DataRuleTypeService: + @staticmethod + async def get(*, pk: int) -> DataRuleType: + async with async_db_session() as db: + data_rule_type = await data_rule_type_dao.get(db, pk) + if not data_rule_type: + raise errors.NotFoundError(msg='数据规则类型不存在') + return data_rule_type + + @staticmethod + async def get_select() -> Select: + return await data_rule_type_dao.get_list() + + @staticmethod + async def get_all() -> Sequence[DataRuleType]: + async with async_db_session() as db: + data_rule_types = await data_rule_type_dao.get_all(db) + return data_rule_types + + @staticmethod + async def create(*, obj: CreateDataRuleTypeParam) -> None: + async with async_db_session.begin() as db: + data_rule_type = await data_rule_type_dao.get_by_name(db, obj.name) + if data_rule_type: + raise errors.ForbiddenError(msg='数据权限规则类型已存在') + await data_rule_type_dao.create(db, obj) + + @staticmethod + async def update(*, pk: int, obj: UpdateDataRuleTypeParam) -> int: + async with async_db_session.begin() as db: + data_rule_type = await data_rule_type_dao.get(db, pk) + if not data_rule_type: + raise errors.NotFoundError(msg='数据权限规则类型不存在') + if data_rule_type.name != obj.name: + if await data_rule_type_dao.get_by_name(db, obj.name): + raise errors.ForbiddenError(msg='数据权限规则类型已存在') + count = await data_rule_type_dao.update(db, pk, obj) + return count + + @staticmethod + async def delete(*, pk: list[int]) -> int: + async with async_db_session.begin() as db: + count = await data_rule_type_dao.delete(db, pk) + return count + + +data_rule_type_service: DataRuleTypeService = DataRuleTypeService() diff --git a/backend/app/admin/service/dict_data_service.py b/backend/app/admin/service/dict_data_service.py index 45bf41c3..4cffd9bc 100644 --- a/backend/app/admin/service/dict_data_service.py +++ b/backend/app/admin/service/dict_data_service.py @@ -31,7 +31,7 @@ async def create(*, obj: CreateDictDataParam) -> None: raise errors.ForbiddenError(msg='字典数据已存在') dict_type = await dict_type_dao.get(db, obj.type_id) if not dict_type: - raise errors.ForbiddenError(msg='字典类型不存在') + raise errors.NotFoundError(msg='字典类型不存在') await dict_data_dao.create(db, obj) @staticmethod @@ -45,7 +45,7 @@ async def update(*, pk: int, obj: UpdateDictDataParam) -> int: raise errors.ForbiddenError(msg='字典数据已存在') dict_type = await dict_type_dao.get(db, obj.type_id) if not dict_type: - raise errors.ForbiddenError(msg='字典类型不存在') + raise errors.NotFoundError(msg='字典类型不存在') count = await dict_data_dao.update(db, pk, obj) return count diff --git a/backend/app/admin/service/menu_service.py b/backend/app/admin/service/menu_service.py index e69225b0..0172f9bd 100644 --- a/backend/app/admin/service/menu_service.py +++ b/backend/app/admin/service/menu_service.py @@ -9,9 +9,7 @@ from backend.app.admin.model import Menu from backend.app.admin.schema.menu import CreateMenuParam, UpdateMenuParam from backend.common.exception import errors -from backend.core.conf import settings from backend.database.db_mysql import async_db_session -from backend.database.db_redis import redis_client from backend.utils.build_tree import get_tree_data @@ -83,7 +81,6 @@ async def update(*, pk: int, obj: UpdateMenuParam) -> int: if obj.parent_id == menu.id: raise errors.ForbiddenError(msg='禁止关联自身为父级') count = await menu_dao.update(db, pk, obj) - await redis_client.delete_prefix(settings.PERMISSION_REDIS_PREFIX) return count @staticmethod diff --git a/backend/app/admin/service/role_service.py b/backend/app/admin/service/role_service.py index 108f57a6..640d503e 100644 --- a/backend/app/admin/service/role_service.py +++ b/backend/app/admin/service/role_service.py @@ -5,10 +5,16 @@ from fastapi import Request from sqlalchemy import Select +from backend.app.admin.crud.crud_data_rule import data_rule_dao from backend.app.admin.crud.crud_menu import menu_dao from backend.app.admin.crud.crud_role import role_dao from backend.app.admin.model import Role -from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam +from backend.app.admin.schema.role import ( + CreateRoleParam, + UpdateRoleMenuParam, + UpdateRoleParam, + UpdateRoleRuleParam, +) from backend.common.exception import errors from backend.core.conf import settings from backend.database.db_mysql import async_db_session @@ -31,9 +37,9 @@ async def get_all() -> Sequence[Role]: return roles @staticmethod - async def get_user_roles(*, pk: int) -> Sequence[Role]: + async def get_by_user(*, pk: int) -> Sequence[Role]: async with async_db_session() as db: - roles = await role_dao.get_user_roles(db, user_id=pk) + roles = await role_dao.get_by_user(db, user_id=pk) return roles @staticmethod @@ -73,7 +79,21 @@ async def update_role_menu(*, request: Request, pk: int, menu_ids: UpdateRoleMen raise errors.NotFoundError(msg='菜单不存在') count = await role_dao.update_menus(db, pk, menu_ids) if pk in [role.id for role in request.user.roles]: - await redis_client.delete_prefix(f'{settings.PERMISSION_REDIS_PREFIX}:{request.user.uuid}') + await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{request.user.id}') + return count + + @staticmethod + async def update_role_rule(*, request: Request, pk: int, rule_ids: UpdateRoleRuleParam) -> int: + async with async_db_session.begin() as db: + role = await role_dao.get(db, pk) + if not role: + raise errors.NotFoundError(msg='角色不存在') + for rule_id in rule_ids.rules: + rule = await data_rule_dao.get(db, rule_id) + if not rule: + raise errors.NotFoundError(msg='数据权限不存在') + count = await role_dao.update_rules(db, pk, rule_ids) + if pk in [role.id for role in request.user.roles]: await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{request.user.id}') return count diff --git a/backend/app/admin/service/user_service.py b/backend/app/admin/service/user_service.py index 5284c108..f751c108 100644 --- a/backend/app/admin/service/user_service.py +++ b/backend/app/admin/service/user_service.py @@ -135,7 +135,6 @@ async def update_roles(*, request: Request, username: str, obj: UpdateUserRolePa 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}') await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{request.user.id}') @staticmethod diff --git a/backend/app/generator/api/router.py b/backend/app/generator/api/router.py index bee65490..b128445d 100644 --- a/backend/app/generator/api/router.py +++ b/backend/app/generator/api/router.py @@ -6,8 +6,8 @@ from backend.app.generator.api.v1.gen_business import router as gen_business_router from backend.app.generator.api.v1.gen_model import router as gen_model_router -v1 = APIRouter() +v1 = APIRouter(prefix='/gen', tags=['代码生成']) -v1.include_router(gen_business_router, prefix='/business', tags=['代码生成']) -v1.include_router(gen_model_router, prefix='/model', tags=['代码生成']) -v1.include_router(gen_router, prefix='/gen', tags=['代码生成']) +v1.include_router(gen_business_router, prefix='/businesses') +v1.include_router(gen_model_router, prefix='/models') +v1.include_router(gen_router) diff --git a/backend/app/generator/api/v1/gen.py b/backend/app/generator/api/v1/gen.py index aa806118..6a978d01 100644 --- a/backend/app/generator/api/v1/gen.py +++ b/backend/app/generator/api/v1/gen.py @@ -16,7 +16,7 @@ router = APIRouter() -@router.get('/tables', summary='获取数据库表', dependencies=[DependsRBAC]) +@router.get('/tables', summary='获取数据库表') async def get_all_tables(table_schema: Annotated[str, Query(..., description='数据库名')] = 'fba') -> ResponseModel: data = await gen_service.get_tables(table_schema=table_schema) return response_base.success(data=data) diff --git a/backend/common/enums.py b/backend/common/enums.py index e60fca30..5661948c 100644 --- a/backend/common/enums.py +++ b/backend/common/enums.py @@ -35,11 +35,24 @@ class MenuType(IntEnum): button = 2 -class RoleDataScopeType(IntEnum): - """数据范围""" +class RoleDataRuleOperatorType(IntEnum): + """数据权限规则运算符""" - all = 1 - custom = 2 + AND = 0 + OR = 1 + + +class RoleDataRuleExpressionType(IntEnum): + """数据权限规则表达式""" + + eq = 0 + ne = 1 + gt = 2 + ge = 3 + lt = 4 + le = 5 + in_ = 6 + not_in = 7 class MethodType(StrEnum): diff --git a/backend/common/model.py b/backend/common/model.py index 4822fa9a..e886a8b7 100644 --- a/backend/common/model.py +++ b/backend/common/model.py @@ -19,8 +19,8 @@ class UserMixin(MappedAsDataclass): """用户 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='修改者') + created_by: Mapped[int] = mapped_column(sort_order=998, comment='创建者') + updated_by: Mapped[int | None] = mapped_column(init=False, default=None, sort_order=998, comment='修改者') class DateTimeMixin(MappedAsDataclass): diff --git a/backend/common/security/jwt.py b/backend/common/security/jwt.py index b9fb80f5..190a8c80 100644 --- a/backend/common/security/jwt.py +++ b/backend/common/security/jwt.py @@ -173,13 +173,13 @@ async def get_current_user(db: AsyncSession, pk: int) -> User: raise AuthorizationError(msg='用户已被锁定,请联系系统管理员') if user.dept_id: if not user.dept.status: - raise AuthorizationError(msg='用户所属部门已锁定') + raise AuthorizationError(msg='用户所属部门已被锁定,请联系系统管理员') if user.dept.del_flag: - raise AuthorizationError(msg='用户所属部门已删除') + raise AuthorizationError(msg='用户所属部门已被删除,请联系系统管理员') if user.roles: role_status = [role.status for role in user.roles] if all(status == 0 for status in role_status): - raise AuthorizationError(msg='用户所属角色已锁定') + raise AuthorizationError(msg='用户所属角色已被锁定,请联系系统管理员') return user diff --git a/backend/common/security/permission.py b/backend/common/security/permission.py index 3db8b550..9f023353 100644 --- a/backend/common/security/permission.py +++ b/backend/common/security/permission.py @@ -1,9 +1,18 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from typing import TYPE_CHECKING + from fastapi import Request +from sqlalchemy import ColumnElement, and_, or_ +from backend.common.enums import RoleDataRuleExpressionType, RoleDataRuleOperatorType +from backend.common.exception import errors from backend.common.exception.errors import ServerError from backend.core.conf import settings +from backend.utils.import_parse import dynamic_import + +if TYPE_CHECKING: + from backend.app.admin.schema.data_rule import GetDataRuleListDetails class RequestPermission: @@ -24,3 +33,78 @@ async def __call__(self, request: Request): raise ServerError # 附加权限标识 request.state.permission = self.value + + +def filter_data_permission(request: Request) -> ColumnElement[bool]: + """ + 过滤数据权限 + + 使用场景:用户登录前台后,控制其能看到哪些数据 + + :param request: + :return: + """ + data_rules = [] + for role in request.user.roles: + data_rules.extend(role.rules) + user_data_rules: list[GetDataRuleListDetails] = list(dict.fromkeys(data_rules)) + + # 超级管理员和无规则用户不做过滤 + if request.user.is_superuser or not user_data_rules: + return or_(1 == 1) + + where_and_list = [] + where_or_list = [] + + for rule in user_data_rules: + rule_model = rule.model + if rule_model not in settings.DATA_PERMISSION_MODELS: + raise errors.NotFoundError(msg='数据规则模型不存在') + model_ins = dynamic_import(settings.DATA_PERMISSION_MODELS[rule_model]) + model_columns = [ + key for key in model_ins.__table__.columns.keys() if key not in settings.DATA_PERMISSION_COLUMN_EXCLUDE + ] + column = rule.column + if column not in model_columns: + raise errors.NotFoundError(msg='数据规则模型列不存在') + + # 获取模型的列对象 + column_obj = getattr(model_ins, column) + rule_expression = rule.expression + + # 根据表达式类型构建条件 + condition = None + if rule_expression == RoleDataRuleExpressionType.eq: + condition = column_obj == rule.value + elif rule_expression == RoleDataRuleExpressionType.ne: + condition = column_obj != rule.value + elif rule_expression == RoleDataRuleExpressionType.gt: + condition = column_obj > rule.value + elif rule_expression == RoleDataRuleExpressionType.ge: + condition = column_obj >= rule.value + elif rule_expression == RoleDataRuleExpressionType.lt: + condition = column_obj < rule.value + elif rule_expression == RoleDataRuleExpressionType.le: + condition = column_obj <= rule.value + elif rule_expression == RoleDataRuleExpressionType.in_: + values = rule.value.split(',') if isinstance(rule.value, str) else rule.value + condition = column_obj.in_(values) + elif rule.expression == RoleDataRuleExpressionType.not_in: + values = rule.value.split(',') if isinstance(rule.value, str) else rule.value + condition = ~column_obj.in_(values) + + if condition is not None: + rule_operator = rule.operator + if rule_operator == RoleDataRuleOperatorType.AND: + where_and_list.append(condition) + elif rule_operator == RoleDataRuleOperatorType.OR: + where_or_list.append(condition) + + # 组合条件 + where_list = [] + if where_and_list: + where_list.append(and_(*where_and_list)) + if where_or_list: + where_list.append(or_(*where_or_list)) + + return or_(*where_list) if where_list else or_(1 == 1) diff --git a/backend/common/security/rbac.py b/backend/common/security/rbac.py index a6972011..2d3b251f 100644 --- a/backend/common/security/rbac.py +++ b/backend/common/security/rbac.py @@ -21,7 +21,7 @@ async def enforcer() -> casbin.AsyncEnforcer: :return: """ - # 规则数据作为死数据直接在方法内定义 + # 模型定义:https://casbin.org/zh/docs/category/model _CASBIN_RBAC_MODEL_CONF_TEXT = """ [request_definition] r = sub, obj, act @@ -46,56 +46,69 @@ async def enforcer() -> casbin.AsyncEnforcer: async def rbac_verify(self, request: Request, _token: str = DependsJwtAuth) -> None: """ - RBAC 权限校验 + RBAC 权限校验(鉴权顺序很重要,谨慎修改) :param request: :param _token: :return: """ path = request.url.path - # 鉴权白名单 + + # API 鉴权白名单 if path in settings.TOKEN_REQUEST_PATH_EXCLUDE: return + # JWT 授权状态强制校验 if not request.auth.scopes: raise TokenError + # 超级管理员免校验 if request.user.is_superuser: return - # 检测角色数据权限范围 + + # 检测用户角色 user_roles = request.user.roles - if not user_roles: - raise AuthorizationError(msg='用户未分配角色,授权失败') + if not user_roles or all(status == 0 for status in user_roles): + raise AuthorizationError(msg='用户未分配角色,请联系系统管理员') + + # 检测用户所属角色菜单 if not any(len(role.menus) > 0 for role in user_roles): - raise AuthorizationError(msg='用户所属角色未分配菜单,授权失败') + raise AuthorizationError(msg='用户未分配菜单,请联系系统管理员') + + # 检测后台管理操作权限 method = request.method if method != MethodType.GET or method != MethodType.OPTIONS: if not request.user.is_staff: - raise AuthorizationError(msg='此用户已被禁止后台管理操作') - # 数据权限范围 - data_scope = any(role.data_scope == 1 for role in user_roles) - if data_scope: - return - user_uuid = request.user.uuid + raise AuthorizationError(msg='用户已被禁止后台管理操作,请联系系统管理员') + + # RBAC 鉴权 if settings.PERMISSION_MODE == 'role-menu': - # 角色菜单权限校验 path_auth_perm = getattr(request.state, 'permission', None) - # 没有菜单权限标识不校验 + + # 没有菜单操作权限标识不校验 if not path_auth_perm: return - if path_auth_perm in set(settings.RBAC_ROLE_MENU_EXCLUDE): + + # 菜单鉴权白名单 + if path_auth_perm in settings.RBAC_ROLE_MENU_EXCLUDE: return + + # 已分配菜单权限校验 allow_perms = [] for role in user_roles: for menu in role.menus: - if menu.status == StatusType.enable: + if menu.perms and menu.status == StatusType.enable: allow_perms.extend(menu.perms.split(',')) if path_auth_perm not in allow_perms: raise AuthorizationError else: - # casbin 权限校验 + # casbin 鉴权白名单 if (method, path) in settings.RBAC_CASBIN_EXCLUDE: return + + # casbin 权限校验 + # 实现机制:backend/app/admin/api/v1/sys/casbin.py + user_uuid = request.user.uuid enforcer = await self.enforcer() if not enforcer.enforce(user_uuid, path, method): raise AuthorizationError diff --git a/backend/core/conf.py b/backend/core/conf.py index 10ef7db5..27407e13 100644 --- a/backend/core/conf.py +++ b/backend/core/conf.py @@ -167,6 +167,19 @@ def validate_openapi_url(cls, values): 'confirm_password', ] + # Data permission + DATA_PERMISSION_MODELS: dict[ + str, str + ] = { # 允许进行数据过滤的 SQLA 模型,它必须以模块字符串的方式定义(它应该只用于前台数据,这里只是为了演示) + 'Api': 'backend.app.admin.model.Api', + } + DATA_PERMISSION_COLUMN_EXCLUDE: list[str] = [ # 排除允许进行数据过滤的 SQLA 模型列 + 'id', + 'sort', + 'created_time', + 'updated_time', + ] + @lru_cache def get_settings() -> Settings: diff --git a/backend/sql/create_tables.sql b/backend/sql/create_tables.sql index 12bba367..fd513a55 100644 --- a/backend/sql/create_tables.sql +++ b/backend/sql/create_tables.sql @@ -1,324 +1,384 @@ --- sys_api: table -CREATE TABLE `sys_api` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `name` varchar(50) NOT NULL COMMENT 'api名称', - `method` varchar(16) NOT NULL COMMENT '请求方法', - `path` varchar(500) NOT NULL COMMENT 'api路径', - `remark` longtext COMMENT '备注', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`), - KEY `ix_sys_api_id` (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_casbin_rule: table -CREATE TABLE `sys_casbin_rule` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `ptype` varchar(255) NOT NULL COMMENT '策略类型: p / g', - `v0` varchar(255) NOT NULL COMMENT '角色ID / 用户uuid', - `v1` longtext NOT NULL COMMENT 'api路径 / 角色名称', - `v2` varchar(255) DEFAULT NULL COMMENT '请求方法', - `v3` varchar(255) DEFAULT NULL, - `v4` varchar(255) DEFAULT NULL, - `v5` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`), - KEY `ix_sys_casbin_rule_id` (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_dept: table -CREATE TABLE `sys_dept` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `name` varchar(50) NOT NULL COMMENT '部门名称', - `level` int NOT NULL COMMENT '部门层级', - `sort` int NOT NULL COMMENT '排序', - `leader` varchar(20) DEFAULT NULL COMMENT '负责人', - `phone` varchar(11) DEFAULT NULL COMMENT '手机', - `email` varchar(50) DEFAULT NULL COMMENT '邮箱', - `status` int NOT NULL COMMENT '部门状态(0停用 1正常)', - `del_flag` tinyint(1) NOT NULL COMMENT '删除标志(0删除 1存在)', - `parent_id` int DEFAULT NULL COMMENT '父部门ID', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `ix_sys_dept_id` (`id`), - KEY `ix_sys_dept_parent_id` (`parent_id`), - CONSTRAINT `sys_dept_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_dict_data: table -CREATE TABLE `sys_dict_data` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `label` varchar(32) NOT NULL COMMENT '字典标签', - `value` varchar(32) NOT NULL COMMENT '字典值', - `sort` int NOT NULL COMMENT '排序', - `status` int NOT NULL COMMENT '状态(0停用 1正常)', - `remark` longtext COMMENT '备注', - `type_id` int NOT NULL COMMENT '字典类型关联ID', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `label` (`label`), - UNIQUE KEY `value` (`value`), - KEY `type_id` (`type_id`), - KEY `ix_sys_dict_data_id` (`id`), - CONSTRAINT `sys_dict_data_ibfk_1` FOREIGN KEY (`type_id`) REFERENCES `sys_dict_type` (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_dict_type: table -CREATE TABLE `sys_dict_type` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `name` varchar(32) NOT NULL COMMENT '字典类型名称', - `code` varchar(32) NOT NULL COMMENT '字典类型编码', - `status` int NOT NULL COMMENT '状态(0停用 1正常)', - `remark` longtext COMMENT '备注', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`), - UNIQUE KEY `code` (`code`), - KEY `ix_sys_dict_type_id` (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_gen_business: table -CREATE TABLE `sys_gen_business` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `app_name` varchar(50) NOT NULL COMMENT '应用名称(英文)', - `table_name_en` varchar(255) NOT NULL COMMENT '表名称(英文)', - `table_name_zh` varchar(255) NOT NULL COMMENT '表名称(中文)', - `table_simple_name_zh` varchar(255) NOT NULL COMMENT '表名称(中文简称)', - `table_comment` varchar(255) DEFAULT NULL COMMENT '表描述', - `schema_name` varchar(255) DEFAULT NULL COMMENT 'Schema 名称 (默认为英文表驼峰)', - `have_datetime_column` tinyint(1) NOT NULL COMMENT '是否存在默认时间列', - `api_version` varchar(20) NOT NULL COMMENT '代码生成 api 版本,默认为 v1', - `gen_path` varchar(255) DEFAULT NULL COMMENT '代码生成路径(默认为 app 根路径)', - `remark` longtext COMMENT '备注', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `table_name_en` (`table_name_en`), - KEY `ix_sys_gen_business_id` (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_gen_model: table -CREATE TABLE `sys_gen_model` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `name` varchar(50) NOT NULL COMMENT '列名称', - `comment` varchar(255) DEFAULT NULL COMMENT '列描述', - `type` varchar(20) NOT NULL COMMENT '列类型', - `default` varchar(50) DEFAULT NULL COMMENT '列默认值', - `sort` int DEFAULT NULL COMMENT '列排序', - `length` int NOT NULL COMMENT '列长度', - `is_pk` tinyint(1) NOT NULL COMMENT '是否主键', - `is_nullable` tinyint(1) NOT NULL COMMENT '是否可为空', - `gen_business_id` int NOT NULL COMMENT '代码生成业务ID', - PRIMARY KEY (`id`), - KEY `gen_business_id` (`gen_business_id`), - KEY `ix_sys_gen_model_id` (`id`), - CONSTRAINT `sys_gen_model_ibfk_1` FOREIGN KEY (`gen_business_id`) REFERENCES `sys_gen_business` (`id`) ON DELETE CASCADE -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_login_log: table -CREATE TABLE `sys_login_log` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `user_uuid` varchar(50) NOT NULL COMMENT '用户UUID', - `username` varchar(20) NOT NULL COMMENT '用户名', - `status` int NOT NULL COMMENT '登录状态(0失败 1成功)', - `ip` varchar(50) NOT NULL COMMENT '登录IP地址', - `country` varchar(50) DEFAULT NULL COMMENT '国家', - `region` varchar(50) DEFAULT NULL COMMENT '地区', - `city` varchar(50) DEFAULT NULL COMMENT '城市', - `user_agent` varchar(255) NOT NULL COMMENT '请求头', - `os` varchar(50) DEFAULT NULL COMMENT '操作系统', - `browser` varchar(50) DEFAULT NULL COMMENT '浏览器', - `device` varchar(50) DEFAULT NULL COMMENT '设备', - `msg` longtext NOT NULL COMMENT '提示消息', - `login_time` datetime NOT NULL COMMENT '登录时间', - `created_time` datetime NOT NULL COMMENT '创建时间', - PRIMARY KEY (`id`), - KEY `ix_sys_login_log_id` (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_menu: table -CREATE TABLE `sys_menu` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `title` varchar(50) NOT NULL COMMENT '菜单标题', - `name` varchar(50) NOT NULL COMMENT '菜单名称', - `level` int NOT NULL COMMENT '菜单层级', - `sort` int NOT NULL COMMENT '排序', - `icon` varchar(100) DEFAULT NULL COMMENT '菜单图标', - `path` varchar(200) DEFAULT NULL COMMENT '路由地址', - `menu_type` int NOT NULL COMMENT '菜单类型(0目录 1菜单 2按钮)', - `component` varchar(255) DEFAULT NULL COMMENT '组件路径', - `perms` varchar(100) DEFAULT NULL COMMENT '权限标识', - `status` int NOT NULL COMMENT '菜单状态(0停用 1正常)', - `show` int NOT NULL COMMENT '是否显示(0否 1是)', - `cache` int NOT NULL COMMENT '是否缓存(0否 1是)', - `remark` longtext COMMENT '备注', - `parent_id` int DEFAULT NULL COMMENT '父菜单ID', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `ix_sys_menu_id` (`id`), - KEY `ix_sys_menu_parent_id` (`parent_id`), - CONSTRAINT `sys_menu_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_menu` (`id`) ON DELETE SET NULL -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_opera_log: table -CREATE TABLE `sys_opera_log` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `username` varchar(20) DEFAULT NULL COMMENT '用户名', - `method` varchar(20) NOT NULL COMMENT '请求类型', - `title` varchar(255) NOT NULL COMMENT '操作模块', - `path` varchar(500) NOT NULL COMMENT '请求路径', - `ip` varchar(50) NOT NULL COMMENT 'IP地址', - `country` varchar(50) DEFAULT NULL COMMENT '国家', - `region` varchar(50) DEFAULT NULL COMMENT '地区', - `city` varchar(50) DEFAULT NULL COMMENT '城市', - `user_agent` varchar(255) NOT NULL COMMENT '请求头', - `os` varchar(50) DEFAULT NULL COMMENT '操作系统', - `browser` varchar(50) DEFAULT NULL COMMENT '浏览器', - `device` varchar(50) DEFAULT NULL COMMENT '设备', - `args` json DEFAULT NULL COMMENT '请求参数', - `status` int NOT NULL COMMENT '操作状态(0异常 1正常)', - `code` varchar(20) NOT NULL COMMENT '操作状态码', - `msg` longtext COMMENT '提示消息', - `cost_time` float NOT NULL COMMENT '请求耗时ms', - `opera_time` datetime NOT NULL COMMENT '操作时间', - `created_time` datetime NOT NULL COMMENT '创建时间', - PRIMARY KEY (`id`), - KEY `ix_sys_opera_log_id` (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_role: table -CREATE TABLE `sys_role` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `name` varchar(20) NOT NULL COMMENT '角色名称', - `data_scope` int DEFAULT NULL COMMENT '权限范围(1:全部数据权限 2:自定义数据权限)', - `status` int NOT NULL COMMENT '角色状态(0停用 1正常)', - `remark` longtext COMMENT '备注', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`), - KEY `ix_sys_role_id` (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_role_menu: table -CREATE TABLE `sys_role_menu` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `role_id` int NOT NULL COMMENT '角色ID', - `menu_id` int NOT NULL COMMENT '菜单ID', - PRIMARY KEY (`id`, `role_id`, `menu_id`), - UNIQUE KEY `ix_sys_role_menu_id` (`id`), - KEY `role_id` (`role_id`), - KEY `menu_id` (`menu_id`), - CONSTRAINT `sys_role_menu_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE, - CONSTRAINT `sys_role_menu_ibfk_2` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`) ON DELETE CASCADE -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_user: table -CREATE TABLE `sys_user` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `uuid` varchar(50) NOT NULL, - `username` varchar(20) NOT NULL COMMENT '用户名', - `nickname` varchar(20) NOT NULL COMMENT '昵称', - `password` varchar(255) DEFAULT NULL COMMENT '密码', - `salt` varchar(5) DEFAULT NULL COMMENT '加密盐', - `email` varchar(50) NOT NULL COMMENT '邮箱', - `is_superuser` tinyint(1) NOT NULL COMMENT '超级权限(0否 1是)', - `is_staff` tinyint(1) NOT NULL COMMENT '后台管理登陆(0否 1是)', - `status` int NOT NULL COMMENT '用户账号状态(0停用 1正常)', - `is_multi_login` tinyint(1) NOT NULL COMMENT '是否重复登陆(0否 1是)', - `avatar` varchar(255) DEFAULT NULL COMMENT '头像', - `phone` varchar(11) DEFAULT NULL COMMENT '手机号', - `join_time` datetime NOT NULL COMMENT '注册时间', - `last_login_time` datetime DEFAULT NULL COMMENT '上次登录', - `dept_id` int DEFAULT NULL COMMENT '部门关联ID', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - UNIQUE KEY `nickname` (`nickname`), - UNIQUE KEY `ix_sys_user_email` (`email`), - UNIQUE KEY `ix_sys_user_username` (`username`), - KEY `dept_id` (`dept_id`), - KEY `ix_sys_user_id` (`id`), - CONSTRAINT `sys_user_ibfk_1` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_user_role: table -CREATE TABLE `sys_user_role` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `user_id` int NOT NULL COMMENT '用户ID', - `role_id` int NOT NULL COMMENT '角色ID', - PRIMARY KEY (`id`, `user_id`, `role_id`), - UNIQUE KEY `ix_sys_user_role_id` (`id`), - KEY `user_id` (`user_id`), - KEY `role_id` (`role_id`), - CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE, - CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; - --- sys_user_social: table -CREATE TABLE `sys_user_social` -( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', - `source` varchar(20) NOT NULL COMMENT '第三方用户来源', - `open_id` varchar(20) DEFAULT NULL COMMENT '第三方用户的 open id', - `uid` varchar(20) DEFAULT NULL COMMENT '第三方用户的 ID', - `union_id` varchar(20) DEFAULT NULL COMMENT '第三方用户的 union id', - `scope` varchar(120) DEFAULT NULL COMMENT '第三方用户授予的权限', - `code` varchar(50) DEFAULT NULL COMMENT '用户的授权 code', - `user_id` int DEFAULT NULL COMMENT '用户关联ID', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `user_id` (`user_id`), - KEY `ix_sys_user_social_id` (`id`), - CONSTRAINT `sys_user_social_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 - COLLATE = utf8mb4_0900_ai_ci; +SET NAMES utf8mb4; + +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for sys_api +-- ---------------------------- +DROP TABLE IF EXISTS `sys_api`; + +CREATE TABLE `sys_api` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(50) NOT NULL COMMENT 'api名称', + `method` varchar(16) NOT NULL COMMENT '请求方法', + `path` varchar(500) NOT NULL COMMENT 'api路径', + `remark` longtext COMMENT '备注', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `ix_sys_api_id` (`id`) +) ENGINE = InnoDB AUTO_INCREMENT = 4 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_casbin_rule +-- ---------------------------- +DROP TABLE IF EXISTS `sys_casbin_rule`; + +CREATE TABLE `sys_casbin_rule` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `ptype` varchar(255) NOT NULL COMMENT '策略类型: p / g', + `v0` varchar(255) NOT NULL COMMENT '角色ID / 用户uuid', + `v1` longtext NOT NULL COMMENT 'api路径 / 角色名称', + `v2` varchar(255) DEFAULT NULL COMMENT '请求方法', + `v3` varchar(255) DEFAULT NULL, + `v4` varchar(255) DEFAULT NULL, + `v5` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `ix_sys_casbin_rule_id` (`id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; + +CREATE TABLE `sys_config` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(20) NOT NULL COMMENT '名称', + `type` varchar(20) DEFAULT NULL COMMENT '类型', + `key` varchar(50) NOT NULL COMMENT '键名', + `value` longtext NOT NULL COMMENT '键值', + `is_frontend` tinyint(1) NOT NULL COMMENT '是否前端', + `remark` longtext COMMENT '备注', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `key` (`key`), + KEY `ix_sys_config_id` (`id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; + +CREATE TABLE `sys_dept` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(50) NOT NULL COMMENT '部门名称', + `level` int NOT NULL COMMENT '部门层级', + `sort` int NOT NULL COMMENT '排序', + `leader` varchar(20) DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) DEFAULT NULL COMMENT '手机', + `email` varchar(50) DEFAULT NULL COMMENT '邮箱', + `status` int NOT NULL COMMENT '部门状态(0停用 1正常)', + `del_flag` tinyint(1) NOT NULL COMMENT '删除标志(0删除 1存在)', + `parent_id` int DEFAULT NULL COMMENT '父部门ID', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `ix_sys_dept_parent_id` (`parent_id`), + KEY `ix_sys_dept_id` (`id`), + CONSTRAINT `sys_dept_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL +) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_data`; + +CREATE TABLE `sys_dict_data` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `label` varchar(32) NOT NULL COMMENT '字典标签', + `value` varchar(32) NOT NULL COMMENT '字典值', + `sort` int NOT NULL COMMENT '排序', + `status` int NOT NULL COMMENT '状态(0停用 1正常)', + `remark` longtext COMMENT '备注', + `type_id` int NOT NULL COMMENT '字典类型关联ID', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `label` (`label`), + UNIQUE KEY `value` (`value`), + KEY `type_id` (`type_id`), + KEY `ix_sys_dict_data_id` (`id`), + CONSTRAINT `sys_dict_data_ibfk_1` FOREIGN KEY (`type_id`) REFERENCES `sys_dict_type` (`id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_type`; + +CREATE TABLE `sys_dict_type` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(32) NOT NULL COMMENT '字典类型名称', + `code` varchar(32) NOT NULL COMMENT '字典类型编码', + `status` int NOT NULL COMMENT '状态(0停用 1正常)', + `remark` longtext COMMENT '备注', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + UNIQUE KEY `code` (`code`), + KEY `ix_sys_dict_type_id` (`id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_gen_business +-- ---------------------------- +DROP TABLE IF EXISTS `sys_gen_business`; + +CREATE TABLE `sys_gen_business` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `app_name` varchar(50) NOT NULL COMMENT '应用名称(英文)', + `table_name_en` varchar(255) NOT NULL COMMENT '表名称(英文)', + `table_name_zh` varchar(255) NOT NULL COMMENT '表名称(中文)', + `table_simple_name_zh` varchar(255) NOT NULL COMMENT '表名称(中文简称)', + `table_comment` varchar(255) DEFAULT NULL COMMENT '表描述', + `schema_name` varchar(255) DEFAULT NULL COMMENT 'Schema 名称 (默认为英文表名称)', + `default_datetime_column` tinyint(1) NOT NULL COMMENT '是否存在默认时间列', + `api_version` varchar(20) NOT NULL COMMENT '代码生成 api 版本,默认为 v1', + `gen_path` varchar(255) DEFAULT NULL COMMENT '代码生成路径(默认为 app 根路径)', + `remark` longtext COMMENT '备注', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `table_name_en` (`table_name_en`), + KEY `ix_sys_gen_business_id` (`id`) +) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_gen_model +-- ---------------------------- +DROP TABLE IF EXISTS `sys_gen_model`; + +CREATE TABLE `sys_gen_model` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(50) NOT NULL COMMENT '列名称', + `comment` varchar(255) DEFAULT NULL COMMENT '列描述', + `type` varchar(20) NOT NULL COMMENT 'SQLA 模型列类型', + `pd_type` varchar(20) NOT NULL COMMENT '列类型对应的 pydantic 类型', + `default` longtext COMMENT '列默认值', + `sort` int DEFAULT NULL COMMENT '列排序', + `length` int NOT NULL COMMENT '列长度', + `is_pk` tinyint(1) NOT NULL COMMENT '是否主键', + `is_nullable` tinyint(1) NOT NULL COMMENT '是否可为空', + `gen_business_id` int NOT NULL COMMENT '代码生成业务ID', + PRIMARY KEY (`id`), + KEY `gen_business_id` (`gen_business_id`), + KEY `ix_sys_gen_model_id` (`id`), + CONSTRAINT `sys_gen_model_ibfk_1` FOREIGN KEY (`gen_business_id`) REFERENCES `sys_gen_business` (`id`) ON DELETE CASCADE +) ENGINE = InnoDB AUTO_INCREMENT = 5 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_login_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_login_log`; + +CREATE TABLE `sys_login_log` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `user_uuid` varchar(50) NOT NULL COMMENT '用户UUID', + `username` varchar(20) NOT NULL COMMENT '用户名', + `status` int NOT NULL COMMENT '登录状态(0失败 1成功)', + `ip` varchar(50) NOT NULL COMMENT '登录IP地址', + `country` varchar(50) DEFAULT NULL COMMENT '国家', + `region` varchar(50) DEFAULT NULL COMMENT '地区', + `city` varchar(50) DEFAULT NULL COMMENT '城市', + `user_agent` varchar(255) NOT NULL COMMENT '请求头', + `os` varchar(50) DEFAULT NULL COMMENT '操作系统', + `browser` varchar(50) DEFAULT NULL COMMENT '浏览器', + `device` varchar(50) DEFAULT NULL COMMENT '设备', + `msg` longtext NOT NULL COMMENT '提示消息', + `login_time` datetime NOT NULL COMMENT '登录时间', + `created_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `ix_sys_login_log_id` (`id`) +) ENGINE = InnoDB AUTO_INCREMENT = 9 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; + +CREATE TABLE `sys_menu` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `title` varchar(50) NOT NULL COMMENT '菜单标题', + `name` varchar(50) NOT NULL COMMENT '菜单名称', + `level` int NOT NULL COMMENT '菜单层级', + `sort` int NOT NULL COMMENT '排序', + `icon` varchar(100) DEFAULT NULL COMMENT '菜单图标', + `path` varchar(200) DEFAULT NULL COMMENT '路由地址', + `menu_type` int NOT NULL COMMENT '菜单类型(0目录 1菜单 2按钮)', + `component` varchar(255) DEFAULT NULL COMMENT '组件路径', + `perms` varchar(100) DEFAULT NULL COMMENT '权限标识', + `status` int NOT NULL COMMENT '菜单状态(0停用 1正常)', + `show` int NOT NULL COMMENT '是否显示(0否 1是)', + `cache` int NOT NULL COMMENT '是否缓存(0否 1是)', + `remark` longtext COMMENT '备注', + `parent_id` int DEFAULT NULL COMMENT '父菜单ID', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `ix_sys_menu_parent_id` (`parent_id`), + KEY `ix_sys_menu_id` (`id`), + CONSTRAINT `sys_menu_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_menu` (`id`) ON DELETE SET NULL +) ENGINE = InnoDB AUTO_INCREMENT = 43 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_opera_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_opera_log`; + +CREATE TABLE `sys_opera_log` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `trace_id` varchar(32) NOT NULL COMMENT '请求跟踪 ID', + `username` varchar(20) DEFAULT NULL COMMENT '用户名', + `method` varchar(20) NOT NULL COMMENT '请求类型', + `title` varchar(255) NOT NULL COMMENT '操作模块', + `path` varchar(500) NOT NULL COMMENT '请求路径', + `ip` varchar(50) NOT NULL COMMENT 'IP地址', + `country` varchar(50) DEFAULT NULL COMMENT '国家', + `region` varchar(50) DEFAULT NULL COMMENT '地区', + `city` varchar(50) DEFAULT NULL COMMENT '城市', + `user_agent` varchar(255) NOT NULL COMMENT '请求头', + `os` varchar(50) DEFAULT NULL COMMENT '操作系统', + `browser` varchar(50) DEFAULT NULL COMMENT '浏览器', + `device` varchar(50) DEFAULT NULL COMMENT '设备', + `args` json DEFAULT NULL COMMENT '请求参数', + `status` int NOT NULL COMMENT '操作状态(0异常 1正常)', + `code` varchar(20) NOT NULL COMMENT '操作状态码', + `msg` longtext COMMENT '提示消息', + `cost_time` float NOT NULL COMMENT '请求耗时(ms)', + `opera_time` datetime NOT NULL COMMENT '操作时间', + `created_time` datetime NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `ix_sys_opera_log_id` (`id`) +) ENGINE = InnoDB AUTO_INCREMENT = 221 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; + +CREATE TABLE `sys_role` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `name` varchar(20) NOT NULL COMMENT '角色名称', + `data_scope` int DEFAULT NULL COMMENT '权限范围(1:全部数据权限 2:自定义数据权限)', + `status` int NOT NULL COMMENT '角色状态(0停用 1正常)', + `remark` longtext COMMENT '备注', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`), + KEY `ix_sys_role_id` (`id`) +) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_role_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_dept`; + +CREATE TABLE `sys_role_dept` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `role_id` int NOT NULL COMMENT '角色ID', + `dept_id` int NOT NULL COMMENT '部门ID', + PRIMARY KEY (`id`, `role_id`, `dept_id`), + UNIQUE KEY `ix_sys_role_dept_id` (`id`), + KEY `role_id` (`role_id`), + KEY `dept_id` (`dept_id`), + CONSTRAINT `sys_role_dept_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE, + CONSTRAINT `sys_role_dept_ibfk_2` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) ON DELETE CASCADE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; + +CREATE TABLE `sys_role_menu` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `role_id` int NOT NULL COMMENT '角色ID', + `menu_id` int NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`id`, `role_id`, `menu_id`), + UNIQUE KEY `ix_sys_role_menu_id` (`id`), + KEY `role_id` (`role_id`), + KEY `menu_id` (`menu_id`), + CONSTRAINT `sys_role_menu_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE, + CONSTRAINT `sys_role_menu_ibfk_2` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`) ON DELETE CASCADE +) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; + +CREATE TABLE `sys_user` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `uuid` varchar(50) NOT NULL, + `username` varchar(20) NOT NULL COMMENT '用户名', + `nickname` varchar(20) NOT NULL COMMENT '昵称', + `password` varchar(255) DEFAULT NULL COMMENT '密码', + `salt` varbinary(255) DEFAULT NULL COMMENT '加密盐', + `email` varchar(50) NOT NULL COMMENT '邮箱', + `is_superuser` tinyint(1) NOT NULL COMMENT '超级权限(0否 1是)', + `is_staff` tinyint(1) NOT NULL COMMENT '后台管理登陆(0否 1是)', + `status` int NOT NULL COMMENT '用户账号状态(0停用 1正常)', + `is_multi_login` tinyint(1) NOT NULL COMMENT '是否重复登陆(0否 1是)', + `avatar` varchar(255) DEFAULT NULL COMMENT '头像', + `phone` varchar(11) DEFAULT NULL COMMENT '手机号', + `join_time` datetime NOT NULL COMMENT '注册时间', + `last_login_time` datetime DEFAULT NULL COMMENT '上次登录', + `dept_id` int DEFAULT NULL COMMENT '部门关联ID', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + UNIQUE KEY `nickname` (`nickname`), + UNIQUE KEY `ix_sys_user_username` (`username`), + UNIQUE KEY `ix_sys_user_email` (`email`), + KEY `dept_id` (`dept_id`), + KEY `ix_sys_user_id` (`id`), + CONSTRAINT `sys_user_ibfk_1` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL +) ENGINE = InnoDB AUTO_INCREMENT = 3 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; + +CREATE TABLE `sys_user_role` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `user_id` int NOT NULL COMMENT '用户ID', + `role_id` int NOT NULL COMMENT '角色ID', + PRIMARY KEY (`id`, `user_id`, `role_id`), + UNIQUE KEY `ix_sys_user_role_id` (`id`), + KEY `user_id` (`user_id`), + KEY `role_id` (`role_id`), + CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE, + CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE +) ENGINE = InnoDB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +-- ---------------------------- +-- Table structure for sys_user_social +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_social`; + +CREATE TABLE `sys_user_social` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', + `source` varchar(20) NOT NULL COMMENT '第三方用户来源', + `open_id` varchar(20) DEFAULT NULL COMMENT '第三方用户的 open id', + `uid` varchar(20) DEFAULT NULL COMMENT '第三方用户的 ID', + `union_id` varchar(20) DEFAULT NULL COMMENT '第三方用户的 union id', + `scope` varchar(120) DEFAULT NULL COMMENT '第三方用户授予的权限', + `code` varchar(50) DEFAULT NULL COMMENT '用户的授权 code', + `user_id` int DEFAULT NULL COMMENT '用户关联ID', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `user_id` (`user_id`), + KEY `ix_sys_user_social_id` (`id`), + CONSTRAINT `sys_user_social_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/backend/sql/init_pytest_data.sql b/backend/sql/init_pytest_data.sql index 81a12002..da5bc0db 100644 --- a/backend/sql/init_pytest_data.sql +++ b/backend/sql/init_pytest_data.sql @@ -1,49 +1,62 @@ -INSERT INTO fba_test.sys_dept (id, name, level, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time) -VALUES (1, 'test', 0, 0, null, null, null, 1, 0, null, '2023-06-26 17:13:45', null); +INSERT INTO `fba_test`.`sys_dept` (`id`, `name`, `level`, `sort`, `leader`, `phone`, `email`, `status`, `del_flag`, `parent_id`, `created_time`, `updated_time`) +VALUES (1, 'test', 0, 0, NULL, NULL, NULL, 1, 0, NULL, '2023-06-26 17:13:45', NULL); -INSERT INTO fba_test.sys_menu (id, title, name, level, sort, icon, path, menu_type, component, perms, status, `show`, cache, remark, parent_id, created_time, updated_time) -VALUES (1, '测试', 'test', 0, 0, '', null, 0, null, null, 0, 0, 1, null, null, '2023-07-27 19:14:10', null), - (2, '仪表盘', 'dashboard', 0, 0, 'IconDashboard', 'dashboard', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:15:45', null), - (3, '工作台', 'Workplace', 0, 0, null, 'workplace', 1, '/dashboard/workplace/index.vue', null, 1, 1, 1, null, 2, '2023-07-27 19:17:59', null), - (4, 'arco官网', 'arcoWebsite', 0, 888, 'IconLink', 'https://arco.design', 1, null, null, 1, 1, 1, null, null, '2023-07-27 19:19:23', null), - (5, '日志', 'log', 0, 66, 'IconBug', 'log', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:19:59', null), - (6, '登录日志', 'Login', 0, 0, null, 'login', 1, '/log/login/index.vue', null, 1, 1, 1, null, 5, '2023-07-27 19:20:56', null), - (7, '操作日志', 'Opera', 0, 0, null, 'opera', 1, '/log/opera/index.vue', null, 1, 1, 1, null, 5, '2023-07-27 19:21:28', null), - (8, '常见问题', 'faq', 0, 999, 'IconQuestion', 'https://arco.design/vue/docs/pro/faq', 1, null, null, 1, 1, 1, null, null, '2023-07-27 19:22:24', null), - (9, '系统管理', 'admin', 0, 6, 'IconSettings', 'admin', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:23:00', null), - (10, '部门管理', 'SysDept', 0, 0, null, 'sys-dept', 1, '/admin/dept/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:23:42', null), - (11, '新增', '', 0, 0, null, null, 2, null, 'sys:dept:add', 1, 1, 1, null, 10, '2024-01-07 11:37:00', null), - (12, '编辑', '', 0, 0, null, null, 2, null, 'sys:dept:edit', 1, 1, 1, null, 10, '2024-01-07 11:37:29', null), - (13, '删除', '', 0, 0, null, null, 2, null, 'sys:dept:del', 1, 1, 1, null, 10, '2024-01-07 11:37:44', null), - (14, 'API管理', 'SysApi', 0, 1, null, 'sys-api', 1, '/admin/api/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:24:12', null), - (15, '新增', '', 0, 0, null, null, 2, null, 'sys:api:add', 1, 1, 1, null, 14, '2024-01-07 11:57:09', null), - (16, '编辑', '', 0, 0, null, null, 2, null, 'sys:api:edit', 1, 1, 1, null, 14, '2024-01-07 11:57:44', null), - (17, '删除', '', 0, 0, null, null, 2, null, 'sys:api:del', 1, 1, 1, null, 14, '2024-01-07 11:57:56', null), - (18, '用户管理', 'SysUser', 0, 0, null, 'sys-user', 1, '/admin/user/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:13', null), - (19, '编辑用户角色', '', 0, 0, null, null, 2, null, 'sys:user:role:edit', 1, 1, 1, null, 18, '2024-01-07 12:04:20', null), - (20, '注销', '', 0, 0, null, null, 2, null, 'sys:user:del', 1, 1, 1, '用户注销 != 用户登出,注销之后用户将从数据库删除', 18, '2024-01-07 02:28:09', null), - (21, '角色管理', 'SysRole', 0, 2, null, 'sys-role', 1, '/admin/role/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:25:45', null), - (22, '新增', '', 0, 0, null, null, 2, null, 'sys:role:add', 1, 1, 1, null, 21, '2024-01-07 11:58:37', null), - (23, '编辑', '', 0, 0, null, null, 2, null, 'sys:role:edit', 1, 1, 1, null, 21, '2024-01-07 11:58:52', null), - (24, '删除', '', 0, 0, null, null, 2, null, 'sys:role:del', 1, 1, 1, null, 21, '2024-01-07 11:59:07', null), - (25, '编辑角色菜单', '', 0, 0, null, null, 2, null, 'sys:role:menu:edit', 1, 1, 1, null, 21, '2024-01-07 01:59:39', null), - (26, '菜单管理', 'SysMenu', 0, 2, null, 'sys-menu', 1, '/admin/menu/index.vue', null, 1, 1, 1, null, 9, '2023-07-27 19:45:29', null), - (27, '新增', '', 0, 0, null, null, 2, null, 'sys:menu:add', 1, 1, 1, null, 26, '2024-01-07 12:01:24', null), - (28, '编辑', '', 0, 0, null, null, 2, null, 'sys:menu:edit', 1, 1, 1, null, 26, '2024-01-07 12:01:34', null), - (29, '删除', '', 0, 0, null, null, 2, null, 'sys:menu:del', 1, 1, 1, null, 26, '2024-01-07 12:01:48', null), - (30, '系统监控', 'monitor', 0, 88, 'IconComputer', 'monitor', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:27:08', null), - (31, 'Redis监控', 'Redis', 0, 0, null, 'redis', 1, '/monitor/redis/index.vue', 'sys:monitor:redis', 1, 1, 1, null, 30, '2023-07-27 19:28:03', null), - (32, '服务器监控', 'Server', 0, 0, null, 'server', 1, '/monitor/server/index.vue', 'sys:monitor:server', 1, 1, 1, null, 30, '2023-07-27 19:28:29', null); +INSERT INTO `fba_test`.`sys_api` (`id`, `name`, `method`, `path`, `remark`, `created_time`, `updated_time`) +VALUES (1, '创建API', 'POST', '/api/v1/apis', NULL, '2024-02-02 11:29:47', NULL), + (2, '删除API', 'DELETE', '/api/v1/apis', NULL, '2024-02-02 11:31:32', NULL), + (3, '编辑API', 'PUT', '/api/v1/apis/{pk}', NULL, '2024-02-02 11:32:22', NULL); -INSERT INTO fba_test.sys_role (id, name, data_scope, status, remark, created_time, updated_time) -VALUES (1, 'test', 2, 1, null, '2023-06-26 17:13:45', null); +INSERT INTO `fba_test`.`sys_menu` (`id`, `title`, `name`, `level`, `sort`, `icon`, `path`, `menu_type`, `component`, `perms`, `status`, `show`, `cache`, `remark`, `parent_id`, `created_time`, `updated_time`) +VALUES (1, '测试', 'test', 0, 0, '', NULL, 0, NULL, NULL, 0, 0, 1, NULL, NULL, '2023-07-27 19:14:10', NULL), + (2, '仪表盘', 'dashboard', 0, 0, 'IconDashboard', 'dashboard', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:15:45', NULL), + (3, '工作台', 'Workplace', 0, 0, NULL, 'workplace', 1, '/dashboard/workplace/index.vue', NULL, 1, 1, 1, NULL, 2, '2023-07-27 19:17:59', NULL), + (4, '日志', 'log', 0, 66, 'IconBug', 'log', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:19:59', NULL), + (5, '登录日志', 'Login', 0, 0, NULL, 'login', 1, '/log/login/index.vue', NULL, 1, 1, 1, NULL, 4, '2023-07-27 19:20:56', NULL), + (6, '操作日志', 'Opera', 0, 0, NULL, 'opera', 1, '/log/opera/index.vue', NULL, 1, 1, 1, NULL, 4, '2023-07-27 19:21:28', NULL), + (7, '系统管理', 'admin', 0, 6, 'IconSettings', 'admin', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:23:00', NULL), + (8, '部门管理', 'SysDept', 0, 0, NULL, 'sys-dept', 1, '/admin/dept/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:23:42', NULL), + (9, '新增', '', 0, 0, NULL, NULL, 2, NULL, 'sys:dept:add', 1, 1, 1, NULL, 8, '2024-01-07 11:37:00', NULL), + (10, '编辑', '', 0, 0, NULL, NULL, 2, NULL, 'sys:dept:edit', 1, 1, 1, NULL, 8, '2024-01-07 11:37:29', NULL), + (11, '删除', '', 0, 0, NULL, NULL, 2, NULL, 'sys:dept:del', 1, 1, 1, NULL, 8, '2024-01-07 11:37:44', NULL), + (12, 'API管理', 'SysApi', 0, 1, NULL, 'sys-api', 1, '/admin/api/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:24:12', NULL), + (13, '新增', '', 0, 0, NULL, NULL, 2, NULL, 'sys:api:add', 1, 1, 1, NULL, 12, '2024-01-07 11:57:09', NULL), + (14, '编辑', '', 0, 0, NULL, NULL, 2, NULL, 'sys:api:edit', 1, 1, 1, NULL, 12, '2024-01-07 11:57:44', NULL), + (15, '删除', '', 0, 0, NULL, NULL, 2, NULL, 'sys:api:del', 1, 1, 1, NULL, 12, '2024-01-07 11:57:56', NULL), + (16, '用户管理', 'SysUser', 0, 0, NULL, 'sys-user', 1, '/admin/user/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:25:13', NULL), + (17, '编辑用户角色', '', 0, 0, NULL, NULL, 2, NULL, 'sys:user:role:edit', 1, 1, 1, NULL, 16, '2024-01-07 12:04:20', NULL), + (18, '注销', '', 0, 0, NULL, NULL, 2, NULL, 'sys:user:del', 1, 1, 1, '用户注销 != 用户登出,注销之后用户将从数据库删除', 16, '2024-01-07 02:28:09', NULL), + (19, '角色管理', 'SysRole', 0, 2, NULL, 'sys-role', 1, '/admin/role/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:25:45', NULL), + (20, '新增', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:add', 1, 1, 1, NULL, 19, '2024-01-07 11:58:37', NULL), + (21, '编辑', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:edit', 1, 1, 1, NULL, 19, '2024-01-07 11:58:52', NULL), + (22, '删除', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:del', 1, 1, 1, NULL, 19, '2024-01-07 11:59:07', NULL), + (23, '编辑角色菜单', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:menu:edit', 1, 1, 1, NULL, 19, '2024-01-07 01:59:39', NULL), + (24, '编辑角色部门', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:dept:edit', 1, 1, 1, NULL, 19, '2024-01-07 01:59:39', NULL), + (25, '菜单管理', 'SysMenu', 0, 2, NULL, 'sys-menu', 1, '/admin/menu/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:45:29', NULL), + (26, '新增', '', 0, 0, NULL, NULL, 2, NULL, 'sys:menu:add', 1, 1, 1, NULL, 25, '2024-01-07 12:01:24', NULL), + (27, '编辑', '', 0, 0, NULL, NULL, 2, NULL, 'sys:menu:edit', 1, 1, 1, NULL, 25, '2024-01-07 12:01:34', NULL), + (28, '删除', '', 0, 0, NULL, NULL, 2, NULL, 'sys:menu:del', 1, 1, 1, NULL, 25, '2024-01-07 12:01:48', NULL), + (29, '系统监控', 'monitor', 0, 88, 'IconComputer', 'monitor', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:27:08', NULL), + (30, 'Redis监控', 'Redis', 0, 0, NULL, 'redis', 1, '/monitor/redis/index.vue', 'sys:monitor:redis', 1, 1, 1, NULL, 29, '2023-07-27 19:28:03', NULL), + (31, '服务器监控', 'Server', 0, 0, NULL, 'server', 1, '/monitor/server/index.vue', 'sys:monitor:server', 1, 1, 1, NULL, 29, '2023-07-27 19:28:29', NULL), + (32, '系统自动化', 'automation', 0, 777, 'IconCodeSquare', 'automation', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2024-07-27 02:06:20', '2024-07-27 02:18:52'), + (33, '代码生成', 'CodeGenerator', 0, 0, NULL, 'code-generator', 1, '/automation/generator/index.vue', NULL, 1, 1, 1, NULL, 32, '2024-07-27 12:24:54', NULL), + (34, '导入', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:import', 1, 1, 1, NULL, 32, '2024-08-04 12:49:58', NULL), + (35, '新增业务', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:business:add', 1, 1, 1, NULL, 32, '2024-08-04 12:51:29', NULL), + (36, '编辑业务', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:business:edit', 1, 1, 1, NULL, 32, '2024-08-04 12:51:45', NULL), + (37, '删除业务', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:business:del', 1, 1, 1, NULL, 32, '2024-08-04 12:52:05', NULL), + (38, '新增模型', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:model:add', 1, 1, 1, NULL, 32, '2024-08-04 12:52:28', NULL), + (39, '编辑模型', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:model:edit', 1, 1, 1, NULL, 32, '2024-08-04 12:52:45', NULL), + (40, '删除模型', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:model:del', 1, 1, 1, NULL, 32, '2024-08-04 12:52:59', NULL), + (41, '生成', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:generate', 1, 1, 1, NULL, 32, '2024-08-04 12:55:03', NULL), + (42, '官网', 'site', 0, 999, 'IconComputer', 'https://fastapi-practices.github.io/fastapi_best_architecture_docs/', 1, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:22:24', NULL), + (43, '赞助', 'sponsor', 0, 9999, 'IconFire', 'https://wu-clan.github.io/sponsor/', 1, NULL, NULL, 1, 1, 1, NULL, NULL, '2024-07-27 12:39:57', NULL); -INSERT INTO fba_test.sys_role_menu (id, role_id, menu_id) -VALUES (1, 1, 1); +INSERT INTO `fba_test`.`sys_role` (`id`, `name`, `data_scope`, `status`, `remark`, `created_time`, `updated_time`) VALUES (1, 'test', 2, 1, NULL, '2023-06-26 17:13:45', NULL); + +INSERT INTO `fba_test`.`sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (1, 1, 1); -- 密码明文:123456 -INSERT INTO fba_test.sys_user (id, uuid, username, nickname, password, salt, email, is_superuser, is_staff, status, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time) -VALUES (1, 'af4c804f-3966-4949-ace2-3bb7416ea926', 'admin', '用户88888', '$2b$12$RJXAtJodRw37ZQGxTPlu0OH.aN5lNXG6yvC4Tp9GIQEBmMY/YCc.m', 'bcNjV', 'admin@example.com', 1, 1, 1, 0, null, null, '2023-06-26 17:13:45', null, 1, '2023-06-26 17:13:45', null); +INSERT INTO `fba_test`.`sys_user` (`id`, `uuid`, `username`, `nickname`, `password`, `salt`, `email`, `is_superuser`, `is_staff`, `status`, `is_multi_login`, `avatar`, `phone`, `join_time`, `last_login_time`, `dept_id`, `created_time`, `updated_time`) +VALUES (1, 'af4c804f-3966-4949-ace2-3bb7416ea926', 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', 0x24326224313224387932654E7563583139566A6D5A33745968424C634F, 'admin@example.com', 1, 1, 1, 0, NULL, NULL, '2023-06-26 17:13:45', '2024-11-18 13:53:57', 1, '2023-06-26 17:13:45', '2024-11-18 13:53:57'); -INSERT INTO fba_test.sys_user_role (id, user_id, role_id) -VALUES (1, 1, 1); +INSERT INTO `fba_test`.`sys_user_role` (`id`, `user_id`, `role_id`) VALUES (1, 1, 1); diff --git a/backend/sql/init_test_data.sql b/backend/sql/init_test_data.sql index 1ec68213..04040607 100644 --- a/backend/sql/init_test_data.sql +++ b/backend/sql/init_test_data.sql @@ -1,59 +1,62 @@ -INSERT INTO fba.sys_dept (id, name, level, sort, leader, phone, email, status, del_flag, parent_id, created_time, updated_time) -VALUES (1, 'test', 0, 0, null, null, null, 1, 0, null, '2023-06-26 17:13:45', null); +INSERT INTO `fba`.`sys_dept` (`id`, `name`, `level`, `sort`, `leader`, `phone`, `email`, `status`, `del_flag`, `parent_id`, `created_time`, `updated_time`) +VALUES (1, 'test', 0, 0, NULL, NULL, NULL, 1, 0, NULL, '2023-06-26 17:13:45', NULL); -INSERT INTO fba.sys_menu (id, title, name, level, sort, icon, path, menu_type, component, perms, status, `show`, cache, remark, parent_id, created_time, updated_time) -VALUES (1, '测试', 'test', 0, 0, '', null, 0, null, null, 0, 0, 1, null, null, '2023-07-27 19:14:10', null), - (2, '仪表盘', 'dashboard', 0, 0, 'IconDashboard', 'dashboard', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:15:45', null), - (3, '工作台', 'Workplace', 0, 0, null, 'workplace', 1, '/dashboard/workplace/index.vue', null, 1, 1, 1, null, 2, '2023-07-27 19:17:59', null), - (4, '日志', 'log', 0, 66, 'IconBug', 'log', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:19:59', null), - (5, '登录日志', 'Login', 0, 0, null, 'login', 1, '/log/login/index.vue', null, 1, 1, 1, null, 4, '2023-07-27 19:20:56', null), - (6, '操作日志', 'Opera', 0, 0, null, 'opera', 1, '/log/opera/index.vue', null, 1, 1, 1, null, 4, '2023-07-27 19:21:28', null), - (7, '系统管理', 'admin', 0, 6, 'IconSettings', 'admin', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:23:00', null), - (8, '部门管理', 'SysDept', 0, 0, null, 'sys-dept', 1, '/admin/dept/index.vue', null, 1, 1, 1, null, 7, '2023-07-27 19:23:42', null), - (9, '新增', '', 0, 0, null, null, 2, null, 'sys:dept:add', 1, 1, 1, null, 8, '2024-01-07 11:37:00', null), - (10, '编辑', '', 0, 0, null, null, 2, null, 'sys:dept:edit', 1, 1, 1, null, 8, '2024-01-07 11:37:29', null), - (11, '删除', '', 0, 0, null, null, 2, null, 'sys:dept:del', 1, 1, 1, null, 8, '2024-01-07 11:37:44', null), - (12, 'API管理', 'SysApi', 0, 1, null, 'sys-api', 1, '/admin/api/index.vue', null, 1, 1, 1, null, 7, '2023-07-27 19:24:12', null), - (13, '新增', '', 0, 0, null, null, 2, null, 'sys:api:add', 1, 1, 1, null, 12, '2024-01-07 11:57:09', null), - (14, '编辑', '', 0, 0, null, null, 2, null, 'sys:api:edit', 1, 1, 1, null, 12, '2024-01-07 11:57:44', null), - (15, '删除', '', 0, 0, null, null, 2, null, 'sys:api:del', 1, 1, 1, null, 12, '2024-01-07 11:57:56', null), - (16, '用户管理', 'SysUser', 0, 0, null, 'sys-user', 1, '/admin/user/index.vue', null, 1, 1, 1, null, 7, '2023-07-27 19:25:13', null), - (17, '编辑用户角色', '', 0, 0, null, null, 2, null, 'sys:user:role:edit', 1, 1, 1, null, 16, '2024-01-07 12:04:20', null), - (18, '注销', '', 0, 0, null, null, 2, null, 'sys:user:del', 1, 1, 1, '用户注销 != 用户登出,注销之后用户将从数据库删除', 16, '2024-01-07 02:28:09', null), - (19, '角色管理', 'SysRole', 0, 2, null, 'sys-role', 1, '/admin/role/index.vue', null, 1, 1, 1, null, 7, '2023-07-27 19:25:45', null), - (20, '新增', '', 0, 0, null, null, 2, null, 'sys:role:add', 1, 1, 1, null, 19, '2024-01-07 11:58:37', null), - (21, '编辑', '', 0, 0, null, null, 2, null, 'sys:role:edit', 1, 1, 1, null, 19, '2024-01-07 11:58:52', null), - (22, '删除', '', 0, 0, null, null, 2, null, 'sys:role:del', 1, 1, 1, null, 19, '2024-01-07 11:59:07', null), - (23, '编辑角色菜单', '', 0, 0, null, null, 2, null, 'sys:role:menu:edit', 1, 1, 1, null, 19, '2024-01-07 01:59:39', null), - (24, '菜单管理', 'SysMenu', 0, 2, null, 'sys-menu', 1, '/admin/menu/index.vue', null, 1, 1, 1, null, 7, '2023-07-27 19:45:29', null), - (25, '新增', '', 0, 0, null, null, 2, null, 'sys:menu:add', 1, 1, 1, null, 24, '2024-01-07 12:01:24', null), - (26, '编辑', '', 0, 0, null, null, 2, null, 'sys:menu:edit', 1, 1, 1, null, 24, '2024-01-07 12:01:34', null), - (27, '删除', '', 0, 0, null, null, 2, null, 'sys:menu:del', 1, 1, 1, null, 24, '2024-01-07 12:01:48', null), - (28, '系统监控', 'monitor', 0, 88, 'IconComputer', 'monitor', 0, null, null, 1, 1, 1, null, null, '2023-07-27 19:27:08', null), - (29, 'Redis监控', 'Redis', 0, 0, null, 'redis', 1, '/monitor/redis/index.vue', 'sys:monitor:redis', 1, 1, 1, null, 28, '2023-07-27 19:28:03', null), - (30, '服务器监控', 'Server', 0, 0, null, 'server', 1, '/monitor/server/index.vue', 'sys:monitor:server', 1, 1, 1, null, 28, '2023-07-27 19:28:29', null), - (31, '系统自动化', 'automation', 0, 777, 'IconCodeSquare', 'automation', 0, null, null, 1, 1, 1, null, null, '2024-07-27 02:06:20', '2024-07-27 02:18:52'), - (32, '代码生成', 'CodeGenerator', 0, 0, null, 'code-generator', 1, '/automation/generator/index.vue', null, 1, 1, 1, null, 31, '2024-07-27 12:24:54', null), - (33, '导入', '', 0, 0, null, null, 2, null, 'gen:code:import', 1, 1, 1, null, 31, '2024-08-04 12:49:58', null), - (34, '新增业务', '', 0, 0, null, null, 2, null, 'gen:code:business:add', 1, 1, 1, null, 31, '2024-08-04 12:51:29', null), - (35, '编辑业务', '', 0, 0, null, null, 2, null, 'gen:code:business:edit', 1, 1, 1, null, 31, '2024-08-04 12:51:45', null), - (36, '删除业务', '', 0, 0, null, null, 2, null, 'gen:code:business:del', 1, 1, 1, null, 31, '2024-08-04 12:52:05', null), - (37, '新增模型', '', 0, 0, null, null, 2, null, 'gen:code:model:add', 1, 1, 1, null, 31, '2024-08-04 12:52:28', null), - (38, '编辑模型', '', 0, 0, null, null, 2, null, 'gen:code:model:edit', 1, 1, 1, null, 31, '2024-08-04 12:52:45', null), - (39, '删除模型', '', 0, 0, null, null, 2, null, 'gen:code:model:del', 1, 1, 1, null, 31, '2024-08-04 12:52:59', null), - (40, '生成', '', 0, 0, null, null, 2, null, 'gen:code:generate', 1, 1, 1, null, 31, '2024-08-04 12:55:03', null), - (41, 'GitHub', 'github', 0, 8888, 'IconGithub', 'https://github.com/wu-clan', 0, null, null, 1, 1, 1, null, null, '2024-07-27 12:32:46', null), - (42, '赞助', 'sponsor', 0, 9999, 'IconFire', 'https://wu-clan.github.io/sponsor/', 0, null, null, 1, 1, 1, null, null, '2024-07-27 12:39:57', null); +INSERT INTO `fba`.`sys_api` (`id`, `name`, `method`, `path`, `remark`, `created_time`, `updated_time`) +VALUES (1, '创建API', 'POST', '/api/v1/apis', NULL, '2024-02-02 11:29:47', NULL), + (2, '删除API', 'DELETE', '/api/v1/apis', NULL, '2024-02-02 11:31:32', NULL), + (3, '编辑API', 'PUT', '/api/v1/apis/{pk}', NULL, '2024-02-02 11:32:22', NULL); -INSERT INTO fba.sys_role (id, name, data_scope, status, remark, created_time, updated_time) -VALUES (1, 'test', 2, 1, null, '2023-06-26 17:13:45', null); +INSERT INTO `fba`.`sys_menu` (`id`, `title`, `name`, `level`, `sort`, `icon`, `path`, `menu_type`, `component`, `perms`, `status`, `show`, `cache`, `remark`, `parent_id`, `created_time`, `updated_time`) +VALUES (1, '测试', 'test', 0, 0, '', NULL, 0, NULL, NULL, 0, 0, 1, NULL, NULL, '2023-07-27 19:14:10', NULL), + (2, '仪表盘', 'dashboard', 0, 0, 'IconDashboard', 'dashboard', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:15:45', NULL), + (3, '工作台', 'Workplace', 0, 0, NULL, 'workplace', 1, '/dashboard/workplace/index.vue', NULL, 1, 1, 1, NULL, 2, '2023-07-27 19:17:59', NULL), + (4, '日志', 'log', 0, 66, 'IconBug', 'log', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:19:59', NULL), + (5, '登录日志', 'Login', 0, 0, NULL, 'login', 1, '/log/login/index.vue', NULL, 1, 1, 1, NULL, 4, '2023-07-27 19:20:56', NULL), + (6, '操作日志', 'Opera', 0, 0, NULL, 'opera', 1, '/log/opera/index.vue', NULL, 1, 1, 1, NULL, 4, '2023-07-27 19:21:28', NULL), + (7, '系统管理', 'admin', 0, 6, 'IconSettings', 'admin', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:23:00', NULL), + (8, '部门管理', 'SysDept', 0, 0, NULL, 'sys-dept', 1, '/admin/dept/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:23:42', NULL), + (9, '新增', '', 0, 0, NULL, NULL, 2, NULL, 'sys:dept:add', 1, 1, 1, NULL, 8, '2024-01-07 11:37:00', NULL), + (10, '编辑', '', 0, 0, NULL, NULL, 2, NULL, 'sys:dept:edit', 1, 1, 1, NULL, 8, '2024-01-07 11:37:29', NULL), + (11, '删除', '', 0, 0, NULL, NULL, 2, NULL, 'sys:dept:del', 1, 1, 1, NULL, 8, '2024-01-07 11:37:44', NULL), + (12, 'API管理', 'SysApi', 0, 1, NULL, 'sys-api', 1, '/admin/api/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:24:12', NULL), + (13, '新增', '', 0, 0, NULL, NULL, 2, NULL, 'sys:api:add', 1, 1, 1, NULL, 12, '2024-01-07 11:57:09', NULL), + (14, '编辑', '', 0, 0, NULL, NULL, 2, NULL, 'sys:api:edit', 1, 1, 1, NULL, 12, '2024-01-07 11:57:44', NULL), + (15, '删除', '', 0, 0, NULL, NULL, 2, NULL, 'sys:api:del', 1, 1, 1, NULL, 12, '2024-01-07 11:57:56', NULL), + (16, '用户管理', 'SysUser', 0, 0, NULL, 'sys-user', 1, '/admin/user/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:25:13', NULL), + (17, '编辑用户角色', '', 0, 0, NULL, NULL, 2, NULL, 'sys:user:role:edit', 1, 1, 1, NULL, 16, '2024-01-07 12:04:20', NULL), + (18, '注销', '', 0, 0, NULL, NULL, 2, NULL, 'sys:user:del', 1, 1, 1, '用户注销 != 用户登出,注销之后用户将从数据库删除', 16, '2024-01-07 02:28:09', NULL), + (19, '角色管理', 'SysRole', 0, 2, NULL, 'sys-role', 1, '/admin/role/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:25:45', NULL), + (20, '新增', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:add', 1, 1, 1, NULL, 19, '2024-01-07 11:58:37', NULL), + (21, '编辑', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:edit', 1, 1, 1, NULL, 19, '2024-01-07 11:58:52', NULL), + (22, '删除', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:del', 1, 1, 1, NULL, 19, '2024-01-07 11:59:07', NULL), + (23, '编辑角色菜单', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:menu:edit', 1, 1, 1, NULL, 19, '2024-01-07 01:59:39', NULL), + (24, '编辑角色部门', '', 0, 0, NULL, NULL, 2, NULL, 'sys:role:dept:edit', 1, 1, 1, NULL, 19, '2024-01-07 01:59:39', NULL), + (25, '菜单管理', 'SysMenu', 0, 2, NULL, 'sys-menu', 1, '/admin/menu/index.vue', NULL, 1, 1, 1, NULL, 7, '2023-07-27 19:45:29', NULL), + (26, '新增', '', 0, 0, NULL, NULL, 2, NULL, 'sys:menu:add', 1, 1, 1, NULL, 25, '2024-01-07 12:01:24', NULL), + (27, '编辑', '', 0, 0, NULL, NULL, 2, NULL, 'sys:menu:edit', 1, 1, 1, NULL, 25, '2024-01-07 12:01:34', NULL), + (28, '删除', '', 0, 0, NULL, NULL, 2, NULL, 'sys:menu:del', 1, 1, 1, NULL, 25, '2024-01-07 12:01:48', NULL), + (29, '系统监控', 'monitor', 0, 88, 'IconComputer', 'monitor', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:27:08', NULL), + (30, 'Redis监控', 'Redis', 0, 0, NULL, 'redis', 1, '/monitor/redis/index.vue', 'sys:monitor:redis', 1, 1, 1, NULL, 29, '2023-07-27 19:28:03', NULL), + (31, '服务器监控', 'Server', 0, 0, NULL, 'server', 1, '/monitor/server/index.vue', 'sys:monitor:server', 1, 1, 1, NULL, 29, '2023-07-27 19:28:29', NULL), + (32, '系统自动化', 'automation', 0, 777, 'IconCodeSquare', 'automation', 0, NULL, NULL, 1, 1, 1, NULL, NULL, '2024-07-27 02:06:20', '2024-07-27 02:18:52'), + (33, '代码生成', 'CodeGenerator', 0, 0, NULL, 'code-generator', 1, '/automation/generator/index.vue', NULL, 1, 1, 1, NULL, 32, '2024-07-27 12:24:54', NULL), + (34, '导入', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:import', 1, 1, 1, NULL, 32, '2024-08-04 12:49:58', NULL), + (35, '新增业务', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:business:add', 1, 1, 1, NULL, 32, '2024-08-04 12:51:29', NULL), + (36, '编辑业务', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:business:edit', 1, 1, 1, NULL, 32, '2024-08-04 12:51:45', NULL), + (37, '删除业务', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:business:del', 1, 1, 1, NULL, 32, '2024-08-04 12:52:05', NULL), + (38, '新增模型', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:model:add', 1, 1, 1, NULL, 32, '2024-08-04 12:52:28', NULL), + (39, '编辑模型', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:model:edit', 1, 1, 1, NULL, 32, '2024-08-04 12:52:45', NULL), + (40, '删除模型', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:model:del', 1, 1, 1, NULL, 32, '2024-08-04 12:52:59', NULL), + (41, '生成', '', 0, 0, NULL, NULL, 2, NULL, 'gen:code:generate', 1, 1, 1, NULL, 32, '2024-08-04 12:55:03', NULL), + (42, '官网', 'site', 0, 999, 'IconComputer', 'https://fastapi-practices.github.io/fastapi_best_architecture_docs/', 1, NULL, NULL, 1, 1, 1, NULL, NULL, '2023-07-27 19:22:24', NULL), + (43, '赞助', 'sponsor', 0, 9999, 'IconFire', 'https://wu-clan.github.io/sponsor/', 1, NULL, NULL, 1, 1, 1, NULL, NULL, '2024-07-27 12:39:57', NULL); -INSERT INTO fba.sys_role_menu (id, role_id, menu_id) -VALUES (1, 1, 1); +INSERT INTO `fba`.`sys_role` (`id`, `name`, `data_scope`, `status`, `remark`, `created_time`, `updated_time`) VALUES (1, 'test', 2, 1, NULL, '2023-06-26 17:13:45', NULL); + +INSERT INTO `fba`.`sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES (1, 1, 1); -- 密码明文:123456 -INSERT INTO fba.sys_user (id, uuid, username, nickname, password, salt, email, is_superuser, is_staff, status, is_multi_login, avatar, phone, join_time, last_login_time, dept_id, created_time, updated_time) -VALUES (1, 'af4c804f-3966-4949-ace2-3bb7416ea926', 'admin', '用户88888', '$2b$12$RJXAtJodRw37ZQGxTPlu0OH.aN5lNXG6yvC4Tp9GIQEBmMY/YCc.m', 'bcNjV', 'admin@example.com', 1, 1, 1, 0, null, null, '2023-06-26 17:13:45', null, 1, '2023-06-26 17:13:45', null); +INSERT INTO `fba`.`sys_user` (`id`, `uuid`, `username`, `nickname`, `password`, `salt`, `email`, `is_superuser`, `is_staff`, `status`, `is_multi_login`, `avatar`, `phone`, `join_time`, `last_login_time`, `dept_id`, `created_time`, `updated_time`) +VALUES (1, 'af4c804f-3966-4949-ace2-3bb7416ea926', 'admin', '用户88888', '$2b$12$8y2eNucX19VjmZ3tYhBLcOsBwy9w1IjBQE4SSqwMDL5bGQVp2wqS.', 0x24326224313224387932654E7563583139566A6D5A33745968424C634F, 'admin@example.com', 1, 1, 1, 0, NULL, NULL, '2023-06-26 17:13:45', '2024-11-18 13:53:57', 1, '2023-06-26 17:13:45', '2024-11-18 13:53:57'); -INSERT INTO fba.sys_user_role (id, user_id, role_id) -VALUES (1, 1, 1); +INSERT INTO `fba`.`sys_user_role` (`id`, `user_id`, `role_id`) VALUES (1, 1, 1); diff --git a/backend/templates/py/crud.jinja b/backend/templates/py/crud.jinja index d0192673..73d32f10 100644 --- a/backend/templates/py/crud.jinja +++ b/backend/templates/py/crud.jinja @@ -12,7 +12,7 @@ from sqlalchemy_crud_plus import CRUDPlus class CRUD{{ table_name_class }}(CRUDPlus[{{ schema_name }}]): async def get(self, db: AsyncSession, pk: int) -> {{ table_name_class }} | None: """ - 获取 {{ schema_name }} + 获取{{ table_name_zh }} :param db: :param pk: @@ -22,7 +22,7 @@ class CRUD{{ table_name_class }}(CRUDPlus[{{ schema_name }}]): async def get_list(self) -> Select: """ - 获取 {{ schema_name }} 列表 + 获取{{ table_name_zh }}列表 :return: """ @@ -30,7 +30,7 @@ class CRUD{{ table_name_class }}(CRUDPlus[{{ schema_name }}]): async def get_all(self, db: AsyncSession) -> Sequence[{{ table_name_class }}]: """ - 获取所有 {{ schema_name }} + 获取所有{{ table_name_zh }} :param db: :return: @@ -39,7 +39,7 @@ class CRUD{{ table_name_class }}(CRUDPlus[{{ schema_name }}]): async def create(self, db: AsyncSession, obj_in: Create{{ schema_name }}Param) -> None: """ - 创建 {{ schema_name }} + 创建{{ table_name_zh }} :param db: :param obj_in: @@ -49,7 +49,7 @@ class CRUD{{ table_name_class }}(CRUDPlus[{{ schema_name }}]): async def update(self, db: AsyncSession, pk: int, obj_in: Update{{ schema_name }}Param) -> int: """ - 更新 {{ schema_name }} + 更新{{ table_name_zh }} :param db: :param pk: @@ -60,7 +60,7 @@ class CRUD{{ table_name_class }}(CRUDPlus[{{ schema_name }}]): async def delete(self, db: AsyncSession, pk: list[int]) -> int: """ - 删除 {{ schema_name }} + 删除{{ table_name_zh }} :param db: :param pk: diff --git a/backend/utils/import_parse.py b/backend/utils/import_parse.py new file mode 100644 index 00000000..62d7a9dd --- /dev/null +++ b/backend/utils/import_parse.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import importlib + +from functools import lru_cache +from typing import Any + +from backend.common.exception import errors + + +def parse_module_str(module_path: str) -> tuple: + """ + Parse a module string into a Python module and class/function. + + :param module_path: + :return: + """ + module_name, class_or_func = module_path.rsplit('.', 1) + return module_name, class_or_func + + +@lru_cache(maxsize=512) +def import_module_cached(module_name: str) -> Any: + """ + 缓存导入模块 + + :param module_name: + :return: + """ + return importlib.import_module(module_name) + + +def dynamic_import(module_path: str) -> Any: + """ + 动态导入 + + :param module_path: + :return: + """ + module_name, obj_name = parse_module_str(module_path) + + try: + module = import_module_cached(module_name) + class_or_func = getattr(module, obj_name) + return class_or_func + except (ImportError, AttributeError): + raise errors.ServerError(msg=f'数据模型 {module_name} 动态导入失败,请联系系统超级管理员')