Skip to content

Refactor the data rule to scope rule #596

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/app/admin/api/v1/sys/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi import APIRouter

from backend.app.admin.api.v1.sys.data_rule import router as data_rule_router
from backend.app.admin.api.v1.sys.data_scope import router as data_scope_router
from backend.app.admin.api.v1.sys.dept import router as dept_router
from backend.app.admin.api.v1.sys.menu import router as menu_router
from backend.app.admin.api.v1.sys.plugin import router as plugin_router
Expand All @@ -18,6 +19,7 @@
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_scope_router, prefix='/data-scopes', tags=['系统数据范围'])
router.include_router(token_router, prefix='/tokens', tags=['系统令牌'])
router.include_router(upload_router, prefix='/upload', tags=['系统上传'])
router.include_router(plugin_router, prefix='/plugin', tags=['系统插件'])
9 changes: 7 additions & 2 deletions backend/app/admin/api/v1/sys/data_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@

from fastapi import APIRouter, Depends, Path, Query

from backend.app.admin.schema.data_rule import CreateDataRuleParam, GetDataRuleDetail, UpdateDataRuleParam
from backend.app.admin.schema.data_rule import (
CreateDataRuleParam,
GetDataRuleColumnDetail,
GetDataRuleDetail,
UpdateDataRuleParam,
)
from backend.app.admin.service.data_rule_service import data_rule_service
from backend.common.pagination import DependsPagination, PageData, paging_data
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
Expand All @@ -25,7 +30,7 @@ async def get_data_rule_models() -> ResponseSchemaModel[list[str]]:
@router.get('/model/{model}/columns', summary='获取数据规则可用模型列', dependencies=[DependsJwtAuth])
async def get_data_rule_model_columns(
model: Annotated[str, Path(description='模型名称')],
) -> ResponseSchemaModel[list[str]]:
) -> ResponseSchemaModel[list[GetDataRuleColumnDetail]]:
models = await data_rule_service.get_columns(model=model)
return response_base.success(data=models)

Expand Down
118 changes: 118 additions & 0 deletions backend/app/admin/api/v1/sys/data_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Annotated

from fastapi import APIRouter, Depends, Path, Query

from backend.app.admin.schema.data_scope import (
CreateDataScopeParam,
GetDataScopeDetail,
GetDataScopeWithRelationDetail,
UpdateDataScopeParam,
UpdateDataScopeRuleParam,
)
from backend.app.admin.service.data_scope_service import data_scope_service
from backend.common.pagination import DependsPagination, PageData, paging_data
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, 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 import CurrentSession

router = APIRouter()


@router.get('/{pk}', summary='获取数据范围详情', dependencies=[DependsJwtAuth])
async def get_data_scope(
pk: Annotated[int, Path(description='数据范围 ID')],
) -> ResponseSchemaModel[GetDataScopeDetail]:
data = await data_scope_service.get(pk=pk)
return response_base.success(data=data)


@router.get('/{pk}/rules', summary='获取数据范围所有规则', dependencies=[DependsJwtAuth])
async def get_data_scope_rules(
pk: Annotated[int, Path(description='数据范围 ID')],
) -> ResponseSchemaModel[GetDataScopeWithRelationDetail]:
data = await data_scope_service.get_rules(pk=pk)
return response_base.success(data=data)


@router.get(
'',
summary='分页获取所有数据范围',
dependencies=[
DependsJwtAuth,
DependsPagination,
],
)
async def get_pagination_data_scopes(
db: CurrentSession,
name: Annotated[str | None, Query(description='范围名称')] = None,
status: Annotated[int | None, Query(description='状态')] = None,
) -> ResponseSchemaModel[PageData[GetDataScopeDetail]]:
data_scope_select = await data_scope_service.get_select(name=name, status=status)
page_data = await paging_data(db, data_scope_select)
return response_base.success(data=page_data)


@router.post(
'',
summary='创建数据范围',
dependencies=[
Depends(RequestPermission('data:scope:add')),
DependsRBAC,
],
)
async def create_data_scope(obj: CreateDataScopeParam) -> ResponseModel:
await data_scope_service.create(obj=obj)
return response_base.success()


@router.put(
'/{pk}',
summary='更新数据范围',
dependencies=[
Depends(RequestPermission('data:scope:edit')),
DependsRBAC,
],
)
async def update_data_scope(
pk: Annotated[int, Path(description='数据范围 ID')], obj: UpdateDataScopeParam
) -> ResponseModel:
count = await data_scope_service.update(pk=pk, obj=obj)
if count > 0:
return response_base.success()
return response_base.fail()


@router.put(
'/{pk}/rules',
summary='更新数据范围规则',
dependencies=[
Depends(RequestPermission('data:scope:rule:edit')),
DependsRBAC,
],
)
async def update_data_scope_rules(
pk: Annotated[int, Path(description='数据范围 ID')], rule_ids: UpdateDataScopeRuleParam
):
count = await data_scope_service.update_data_scope_rule(pk=pk, rule_ids=rule_ids)
if count > 0:
return response_base.success()
return response_base.fail()


@router.delete(
'',
summary='批量删除数据范围',
dependencies=[
Depends(RequestPermission('data:scope:del')),
DependsRBAC,
],
)
async def delete_data_scope(pk: Annotated[list[int], Query(description='数据范围 ID 列表')]) -> ResponseModel:
count = await data_scope_service.delete(pk=pk)
if count > 0:
return response_base.success()
return response_base.fail()
5 changes: 3 additions & 2 deletions backend/app/admin/api/v1/sys/dept.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
from typing import Annotated, Any

from fastapi import APIRouter, Depends, Path, Query
from fastapi import APIRouter, Depends, Path, Query, Request

from backend.app.admin.schema.dept import CreateDeptParam, GetDeptDetail, UpdateDeptParam
from backend.app.admin.service.dept_service import dept_service
Expand All @@ -22,12 +22,13 @@ async def get_dept(pk: Annotated[int, Path(description='部门 ID')]) -> Respons

@router.get('', summary='获取所有部门展示树', dependencies=[DependsJwtAuth])
async def get_all_depts(
request: Request,
name: Annotated[str | None, Query(description='部门名称')] = None,
leader: Annotated[str | None, Query(description='部门负责人')] = None,
phone: Annotated[str | None, Query(description='联系电话')] = None,
status: Annotated[int | None, Query(description='状态')] = None,
) -> ResponseSchemaModel[list[dict[str, Any]]]:
dept = await dept_service.get_dept_tree(name=name, leader=leader, phone=phone, status=status)
dept = await dept_service.get_dept_tree(request=request, name=name, leader=leader, phone=phone, status=status)
return response_base.success(data=dept)


Expand Down
26 changes: 12 additions & 14 deletions backend/app/admin/api/v1/sys/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@
GetRoleWithRelationDetail,
UpdateRoleMenuParam,
UpdateRoleParam,
UpdateRoleRuleParam,
UpdateRoleScopeParam,
)
from backend.app.admin.service.data_rule_service import data_rule_service
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, PageData, paging_data
from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base
Expand All @@ -35,21 +33,21 @@ async def get_all_roles() -> ResponseSchemaModel[list[GetRoleDetail]]:
async def get_user_all_roles(
pk: Annotated[int, Path(description='用户 ID')],
) -> ResponseSchemaModel[list[GetRoleDetail]]:
data = await role_service.get_by_user(pk=pk)
data = await role_service.get_users(pk=pk)
return response_base.success(data=data)


@router.get('/{pk}/menus', summary='获取角色所有菜单', dependencies=[DependsJwtAuth])
async def get_role_all_menus(
pk: Annotated[int, Path(description='角色 ID')],
) -> ResponseSchemaModel[list[dict[str, Any]]]:
menu = await menu_service.get_role_menu_tree(pk=pk)
menu = await role_service.get_menu_tree(pk=pk)
return response_base.success(data=menu)


@router.get('/{pk}/rules', summary='获取角色所有数据规则', dependencies=[DependsJwtAuth])
async def get_role_all_rules(pk: Annotated[int, Path(description='角色 ID')]) -> ResponseSchemaModel[list[int]]:
rule = await data_rule_service.get_role_rules(pk=pk)
@router.get('/{pk}/scopes', summary='获取角色所有数据范围', dependencies=[DependsJwtAuth])
async def get_role_all_scopes(pk: Annotated[int, Path(description='角色 ID')]) -> ResponseSchemaModel[list[int]]:
rule = await role_service.get_scopes(pk=pk)
return response_base.success(data=rule)


Expand Down Expand Up @@ -125,17 +123,17 @@ async def update_role_menus(


@router.put(
'/{pk}/rule',
summary='更新角色数据规则',
'/{pk}/scope',
summary='更新角色数据范围',
dependencies=[
Depends(RequestPermission('sys:role:rule:edit')),
Depends(RequestPermission('sys:role:scope:edit')),
DependsRBAC,
],
)
async def update_role_rules(
pk: Annotated[int, Path(description='角色 ID')], rule_ids: UpdateRoleRuleParam
async def update_role_scopes(
pk: Annotated[int, Path(description='角色 ID')], scope_ids: UpdateRoleScopeParam
) -> ResponseModel:
count = await role_service.update_role_rule(pk=pk, rule_ids=rule_ids)
count = await role_service.update_role_scope(pk=pk, scope_ids=scope_ids)
if count > 0:
return response_base.success()
return response_base.fail()
Expand Down
1 change: 0 additions & 1 deletion backend/app/admin/api/v1/sys/upload.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from typing import Annotated

from fastapi import APIRouter, File, UploadFile
Expand Down
2 changes: 1 addition & 1 deletion backend/app/admin/crud/crud_data_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ async def get_list(self, name: str | None) -> Select:
:param name: 规则名称
:return:
"""
stmt = select(self.model).options(noload(self.model.roles)).order_by(desc(self.model.created_time))
stmt = select(self.model).options(noload(self.model.scope)).order_by(desc(self.model.created_time))

filters = []
if name is not None:
Expand Down
119 changes: 119 additions & 0 deletions backend/app/admin/crud/crud_data_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from sqlalchemy import Select, and_, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import noload, selectinload
from sqlalchemy_crud_plus import CRUDPlus

from backend.app.admin.model import DataRule, DataScope
from backend.app.admin.schema.data_scope import CreateDataScopeParam, UpdateDataScopeParam, UpdateDataScopeRuleParam


class CRUDDataScope(CRUDPlus[DataScope]):
"""数据范围数据库操作类"""

async def get(self, db: AsyncSession, pk: int) -> DataScope | None:
"""
获取数据范围详情

:param db: 数据库会话
:param pk: 范围 ID
:return:
"""
return await self.select_model(db, pk)

async def get_by_name(self, db: AsyncSession, name: str) -> DataScope | None:
"""
通过名称获取数据范围

:param db: 数据库会话
:param name: 范围名称
:return:
"""
return await self.select_model_by_column(db, name=name)

async def get_with_relation(self, db: AsyncSession, pk: int) -> DataScope:
"""
获取数据范围关联数据

:param db: 数据库会话
:param pk: 范围 ID
:return:
"""
stmt = select(self.model).options(selectinload(self.model.rules)).where(self.model.id == pk)
data_scope = await db.execute(stmt)
return data_scope.scalars().first()

async def get_list(self, name: str | None, status: int | None) -> Select:
"""
获取数据范围列表

:param name: 范围名称
:param status: 范围状态
:return:
"""
stmt = (
select(self.model)
.options(noload(self.model.rules), noload(self.model.roles))
.order_by(desc(self.model.created_time))
)

filters = []
if name is not None:
filters.append(self.model.name.like(f'%{name}%'))
if status is not None:
filters.append(self.model.status == status)

if filters:
stmt = stmt.where(and_(*filters))

return stmt

async def create(self, db: AsyncSession, obj: CreateDataScopeParam) -> None:
"""
创建数据范围

:param db: 数据库会话
:param obj: 创建数据范围参数
:return:
"""
await self.create_model(db, obj)

async def update(self, db: AsyncSession, pk: int, obj: UpdateDataScopeParam) -> int:
"""
更新数据范围

:param db: 数据库会话
:param pk: 范围 ID
:param obj: 更新数据范围参数
:return:
"""
return await self.update_model(db, pk, obj)

async def update_rules(self, db: AsyncSession, pk: int, rule_ids: UpdateDataScopeRuleParam) -> int:
"""
更新数据范围规则

:param db: 数据库会话
:param pk: 范围 ID
:param rule_ids: 数据规则 ID 列表
:return:
"""
current_data_scope = await self.get_with_relation(db, pk)
stmt = select(DataRule).where(DataRule.id.in_(rule_ids.rules))
rules = await db.execute(stmt)
current_data_scope.rules = rules.scalars().all()
return len(current_data_scope.rules)

async def delete(self, db: AsyncSession, pk: list[int]) -> int:
"""
删除数据范围

:param db: 数据库会话
:param pk: 范围 ID 列表
:return:
"""
return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk)


data_scope_dao: CRUDDataScope = CRUDDataScope(DataScope)
Loading