Browse Source

数据安全模块优化

master
si@aidatagov.com 4 months ago
parent
commit
694543552f
  1. 14
      vue-fastapi-backend/module_admin/controller/metasecurity_controller.py
  2. 35
      vue-fastapi-backend/module_admin/dao/metaSecurity_dao.py
  3. 6
      vue-fastapi-backend/module_admin/entity/do/metasecurity_do.py
  4. 32
      vue-fastapi-backend/module_admin/entity/vo/metasecurity_vo.py
  5. 115
      vue-fastapi-backend/module_admin/service/metasecurity_service.py
  6. 44
      vue-fastapi-frontend/src/views/meta/metasercurity/MetaSecurityCol.vue
  7. 45
      vue-fastapi-frontend/src/views/meta/metasercurity/MetaSecurityRow.vue

14
vue-fastapi-backend/module_admin/controller/metasecurity_controller.py

@ -7,7 +7,7 @@ from config.get_db import get_db
from module_admin.entity.vo.user_vo import CurrentUserModel
from module_admin.annotation.log_annotation import Log
from module_admin.aspect.interface_auth import CheckUserInterfaceAuth
from module_admin.entity.vo.metasecurity_vo import MetaSecurityColModel, MetaSecurityRowModel, MetaSecurityColPageQueryModel, MetaSecurityRowPageQueryModel, DeleteMetaSecurityModel
from module_admin.entity.vo.metasecurity_vo import MetaSecurityColModel, MetaSecurityApiModel,MetaSecurityRowModel, MetaSecurityColPageQueryModel, MetaSecurityRowPageQueryModel, DeleteMetaSecurityModel
from module_admin.service.metasecurity_service import MetaSecurityService
from module_admin.service.login_service import LoginService
from utils.common_util import bytes2file_response
@ -176,3 +176,15 @@ async def query_meta_security_row_detail(
row_detail_result = await MetaSecurityService.get_meta_security_row_by_id_services(query_db, row_id)
logger.info(f'获取row_id为{row_id}的行配置信息成功')
return ResponseUtil.success(data=row_detail_result)
# 数据安全接口
@metaSecurityController.get(
'/getMetaSercuitybysql'
)
@ValidateFields(validate_model='apiModel')
async def getMetaSercuitybysql(request: Request, apiModel: MetaSecurityApiModel=Depends(MetaSecurityApiModel), query_db: AsyncSession = Depends(get_db)):
config_detail_result = await MetaSecurityService.getMetaSercuitybysql(request,query_db, apiModel)
logger.info(f'获取config_id为{apiModel}的信息成功')
return ResponseUtil.success(data=config_detail_result)

35
vue-fastapi-backend/module_admin/dao/metaSecurity_dao.py

@ -27,6 +27,8 @@ class MetaSecurityDao:
filters.append(MetaSecurityCol.obj_value.like(f"%{query_object.obj_value}%"))
if query_object.dbTName:
filters.append(MetaSecurityCol.dbTName.like(f"%{query_object.dbTName}%"))
if query_object.obj_name:
filters.append(MetaSecurityCol.obj_name.like(f"%{query_object.obj_name}%"))
if query_object.dbSName:
filters.append(MetaSecurityCol.dbSName.like(f"%{query_object.dbSName}%"))
if query_object.dbRID:
@ -45,7 +47,6 @@ class MetaSecurityDao:
return col_list
@classmethod
async def get_meta_security_row_list(cls, db: AsyncSession, query_object: MetaSecurityRowModel, is_page: bool = False):
"""
@ -65,6 +66,8 @@ class MetaSecurityDao:
filters.append(MetaSecurityRow.dbTName.like(f"%{query_object.dbTName}%"))
if query_object.dbSName:
filters.append(MetaSecurityRow.dbSName.like(f"%{query_object.dbSName}%"))
if query_object.obj_name:
filters.append(MetaSecurityRow.obj_name.like(f"%{query_object.obj_name}%"))
if query_object.dbRID:
filters.append(MetaSecurityRow.dbRID==query_object.dbRID)
# 构建查询语句
@ -80,6 +83,36 @@ class MetaSecurityDao:
)
return row_list
@classmethod
async def get_api_col_list(cls, db: AsyncSession, dbRId: int,dbTname:str,objType:str,objValue:str):
colList = (
await db.execute(
select(MetaSecurityCol)
.where(
(MetaSecurityCol.isStop == 0) &
(MetaSecurityCol.dbRID == dbRId) &
(MetaSecurityCol.obj_type == objType) &
(MetaSecurityCol.dbTName == dbTname) &
(MetaSecurityCol.obj_value == objValue)
)
)
).scalars().all()
return colList
@classmethod
async def get_api_row_list(cls, db: AsyncSession, dbRId: int,dbTname:str,objType:str,objValue:str):
colList = (
await db.execute(
select(MetaSecurityRow)
.where(
(MetaSecurityRow.isStop == 0) &
(MetaSecurityRow.dbRID == dbRId) &
(MetaSecurityRow.obj_type == objType) &
(MetaSecurityRow.dbTName == dbTname) &
(MetaSecurityRow.obj_value == objValue)
)
)
).scalars().all()
return colList
@classmethod
async def get_meta_security_col_by_id(cls, db: AsyncSession, colId: str):

6
vue-fastapi-backend/module_admin/entity/do/metasecurity_do.py

@ -17,11 +17,12 @@ class MetaSecurityCol(Base):
dbRName = Column(String(50), default=None, comment='数据源名称')
dbRID = Column(Integer, nullable=True, default=None, comment='数据源ID')
dbSName = Column(String(50), default=None, comment='模式名称')
dbTName = Column(String(50), default=None, comment='表名称')
dbCName = Column(String(50), default=None, comment='字段名称')
dbTName = Column(String(200), default=None, comment='表名称')
dbCName = Column(String(500), default=None, comment='字段名称')
ctrl_type = Column(String(1), default=None, comment='控制类型(0:反向 1:正向)')
obj_type = Column(String(1), default=None, comment='对象类型(0:角色 1:用户)')
obj_value = Column(Integer, default=None, comment='角色值')
obj_name = Column(String(50), default=None, comment='字段名称')
isStop = Column(Boolean, nullable=True, default=None, comment='是否停用(0:运行 1:停用)')
@ -46,6 +47,7 @@ class MetaSecurityRow(Base):
ctrl_type = Column(String(1), default=None, comment='控制类型(0:控制值 1:控制表 2:控制程序)')
obj_type = Column(String(1), default=None, comment='对象类型(0:角色 1:用户)')
obj_value = Column(String(50), default=None, comment='角色值')
obj_name = Column(String(50), default=None, comment='字段名称')
isStop = Column(Boolean, nullable=True, default=None, comment='是否停用(0:运行 1:停用)')
ctrl_value = Column(String(100), default=None, comment='控制值')
ctrl_table = Column(String(100), default=None, comment='控制表')

32
vue-fastapi-backend/module_admin/entity/vo/metasecurity_vo.py

@ -4,6 +4,7 @@ from datetime import datetime
from module_admin.annotation.pydantic_annotation import as_query
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_validation_decorator import NotBlank, Size
class CrudMetaSecurityModel(BaseModel):
@ -29,6 +30,7 @@ class MetaSecurityRowModel(BaseModel):
ctrl_type: Optional[str] = None # control type (e.g., '0', '1', '2')
obj_type: Optional[str] = None # object type (e.g., '0', '1')
obj_value: Optional[object] = None
obj_name: Optional[object] = None
isStop: Optional[bool] = None
ctrl_value: Optional[str] = None
ctrl_table: Optional[str] = None
@ -55,6 +57,7 @@ class MetaSecurityColModel(BaseModel):
ctrl_type: Optional[str] = None # control type (e.g., '0', '1')
obj_type: Optional[str] = None # object type (e.g., '0', '1')
obj_value: Optional[object] = None
obj_name: Optional[object] = None
isStop: Optional[bool] = None
@as_query
class MetaSecurityRowPageQueryModel(MetaSecurityRowModel):
@ -77,3 +80,32 @@ class DeleteMetaSecurityModel(BaseModel):
"""
model_config = ConfigDict(alias_generator=to_camel)
metaSecurity_ids: str = Field(description='需要删除的参数主键')
class MetaSecurityApiModel(BaseModel):
"""
数据源行控制表对应Pydantic模型
"""
model_config = ConfigDict(alias_generator=to_camel, from_attributes=True)
dbRId: Optional[int] = None
username: Optional[str] = Field(default=None, description='用户名称')
password: Optional[str] = Field(default=None, description='用户密码')
tableName: Optional[str] = Field(default=None, description='表名')
sqlStr: Optional[str] = Field(default=None, description='sql')
@NotBlank(field_name='username', message='用户名称不能为空')
@Size(field_name='username', min_length=0, max_length=100, message='用户名称长度不能超过100个字符')
def get_username(self):
return self.username
@NotBlank(field_name='password', message='用户密码不能为空')
def get_password(self):
return self.password
@NotBlank(field_name='tableName', message='表名不能为空')
def get_tableName(self):
return self.tableName
@NotBlank(field_name='sqlStr', message='sql不能为空')
def get_sqlStr(self):
return self.username
def validate_fields(self):
self.get_username()
self.get_password()
self.get_tableName()
self.get_sqlStr()

115
vue-fastapi-backend/module_admin/service/metasecurity_service.py

@ -1,14 +1,18 @@
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
from config.constant import CommonConstant
from exceptions.exception import ServiceException
from module_admin.dao.metaSecurity_dao import MetaSecurityDao
from module_admin.entity.vo.common_vo import CrudResponseModel
from module_admin.entity.vo.metasecurity_vo import MetaSecurityColModel, MetaSecurityRowModel,DeleteMetaSecurityModel
from module_admin.entity.vo.metasecurity_vo import MetaSecurityColModel, MetaSecurityRowModel,DeleteMetaSecurityModel,MetaSecurityApiModel
from utils.common_util import CamelCaseUtil
import uuid
from module_admin.dao.login_dao import login_by_account
from module_admin.dao.user_dao import UserDao
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import text
import json
class MetaSecurityService:
"""
@ -90,10 +94,12 @@ class MetaSecurityService:
try:
if isinstance(page_object.obj_value, str) and page_object.obj_value:
obj_values = page_object.obj_value.split(",")
for value in obj_values:
obj_names = page_object.obj_name.split(",")
for value, name in zip(obj_values, obj_names):
# 创建新的 page_object 实例,避免修改原始对象
new_page_object = MetaSecurityColModel(**page_object.model_dump(by_alias=True))
new_page_object.obj_value = value.strip() # 去除空格并赋值
new_page_object.obj_name = name.strip() # 去除空格并赋值
new_page_object.colId = str(uuid.uuid4())
# 调用 DAO 方法插入数据
await MetaSecurityDao.add_meta_security_col(query_db, new_page_object)
@ -116,10 +122,12 @@ class MetaSecurityService:
try:
if isinstance(page_object.obj_value, str) and page_object.obj_value:
obj_values = page_object.obj_value.split(",")
for value in obj_values:
obj_names = page_object.obj_name.split(",")
for value, name in zip(obj_values, obj_names):
# 创建新的 page_object 实例,避免修改原始对象
new_page_object = MetaSecurityRowModel(**page_object.model_dump(by_alias=True))
new_page_object.obj_value = value.strip() # 去除空格并赋值
new_page_object.obj_name = name.strip() # 去除空格并赋值
new_page_object.rowId = str(uuid.uuid4())
# 调用 DAO 方法插入数据
await MetaSecurityDao.add_meta_security_row(query_db, new_page_object)
@ -266,5 +274,100 @@ class MetaSecurityService:
raise e
else:
raise ServiceException(message='传入行配置ID为空')
@classmethod
async def getMetaSercuitybysql(cls, request: Request, query_db: AsyncSession, page_object: MetaSecurityApiModel):
#1.校验用户
if not page_object.username:
raise ServiceException(data='', message='用户名不能为空!')
user = await login_by_account(query_db, page_object.username)
if not user:
raise ServiceException(data='', message='用户不存在')
if not page_object.password == user[0].password:
raise ServiceException(data='', message='用户密码错误!')
query_user = await UserDao.get_user_by_id(query_db, user_id=user[0].user_id)
role_id_list = [item.role_id for item in query_user.get('user_role_info')]
#2.查询用户及该用户角色下的所有行列配置
# 列配置
user_col_list=await MetaSecurityDao.get_api_col_list(query_db, page_object.dbRId,page_object.tableName, '0',user[0].user_id)
role_col_list = []
for role_id in role_id_list:
role_cols = await MetaSecurityDao.get_api_col_list(
query_db, page_object.dbRId, page_object.tableName, '1', role_id
)
role_col_list.extend(role_cols) # 将每个角色的列配置合并到列表中
# 行配置
user_row_list=await MetaSecurityDao.get_api_row_list(query_db, page_object.dbRId,page_object.tableName, '0',user[0].user_id)
role_row_list = []
for role_id in role_id_list:
role_rows = await MetaSecurityDao.get_api_row_list(
query_db, page_object.dbRId, page_object.tableName, '1', role_id
)
role_row_list.extend(role_rows) # 将每个角色的行配置合并到列表中
result = {
"user_col_list": user_col_list,
"role_col_list": role_col_list,
"role_row_list": role_row_list,
"user_row_list": user_row_list
}
# return result
#3.根据行列配置控制原始sql
#4.测试数据源连接是否正常
dataParams ={"user":"dbf","password":"1q2w3e4r","address":"jdbc:mysql://47.113.147.166:3306","database":"dash_test_w","jdbcUrl":"jdbc:mysql://47.113.147.166:3306/dash_test_w","driverClassName":"com.mysql.cj.jdbc.Driver","validationQuery":"select 1"}
dbConnent = cls.get_db_engine('mysql',dataParams)
query = "SELECT * FROM msq_table_constraints"
result = await cls.execute_sql(dbConnent, query)
return result
#5.执行原始sql
#6.执行控制后的sql
#7.执行结果
def get_db_engine(db_type: str, db_params: dict):
try:
if db_type == "mysql":
address = db_params['address']
if address.startswith("jdbc:mysql://"):
# 去掉 jdbc:mysql:// 部分
address = address[len("jdbc:mysql://"):]
db_params['address'] = address
conn_str=f"mysql+aiomysql://{db_params['user']}:{db_params['password']}@{db_params['address']}/{db_params['database']}"
print(f"数据库连接字符串: {conn_str}") # 输出调试信息
return create_async_engine(conn_str)
elif db_type == "postgresql":
return create_async_engine(f"postgresql+asyncpg://{db_params['user']}:{db_params['password']}@{db_params['address']}/{db_params['database']}")
# 你可以根据需求添加更多数据库类型
else:
raise ValueError("不支持的数据库类型")
except SQLAlchemyError as e:
# 捕获SQLAlchemy相关的数据库连接错误
raise ConnectionError(f"数据库连接失败: {e}")
except Exception as e:
# 捕获其他非预期的错误
raise RuntimeError(f"连接过程中发生了未知错误: {e}")
@classmethod
async def execute_sql(cls, dbConnent, sql_query: str,sql_type: str):
# 创建异步会话
async with dbConnent.begin():
# 获取会话对象
async_session = sessionmaker(
dbConnent, class_=AsyncSession, expire_on_commit=False
)
async with async_session() as session:
try:
# 执行原始SQL查询
query = text(sql_query)
result = await session.execute(query)
# 获取所有结果
rows = result.fetchall()
# 获取列名
columns = result.keys()
# 将每一行转化为字典,键为列名
result_dict = [dict(zip(columns, row)) for row in rows]
# 转换为 JSON 字符串
return json.dumps(result_dict, ensure_ascii=False, indent=4)
except SQLAlchemyError as e:
raise RuntimeError(f"{sql_type}执行 SQL 查询时发生错误: {e}")

44
vue-fastapi-frontend/src/views/meta/metasercurity/MetaSecurityCol.vue

@ -38,19 +38,21 @@
<!-- 搜索框 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" >
<el-form-item label="模式名" prop="dbSName">
<el-input v-model="queryParams.dbSName" placeholder="请输入模式名称" clearable />
<el-input v-model="queryParams.dbSName" placeholder="请输入模式名称" clearable style="width: 220px"/>
</el-form-item>
<el-form-item label="表名" prop="dbTName">
<el-input v-model="queryParams.dbTName" placeholder="请输入表名称" clearable />
<el-input v-model="queryParams.dbTName" placeholder="请输入表名称" clearable style="width: 220px"/>
</el-form-item>
<el-form-item label="字段名" prop="dbCName">
<el-input v-model="queryParams.dbCName" placeholder="请输入字段名称" clearable />
<el-input v-model="queryParams.dbCName" placeholder="请输入字段名称" clearable style="width: 220px"/>
</el-form-item>
<el-form-item label="对象编号" prop="objValue">
<el-input v-model="queryParams.objValue" placeholder="请输入列名称" clearable />
<el-input v-model="queryParams.objName" placeholder="请输入对象编号" clearable style="width: 220px"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
@ -122,7 +124,13 @@
</span>
</template>
</el-table-column>
<el-table-column label="是否停用" align="center" prop="isStop" />
<el-table-column label="是否停用" align="center" prop="isStop" >
<template #default="scope">
<span >
{{ scope.row.isStop?"是":"否" }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="180">
<template #default="scope">
<el-button @click="handleEdit(scope.row)" type="text" icon="Edit" />
@ -198,6 +206,7 @@
<el-form-item label="对象编号" prop="objValue">
<el-select
v-model="form.objValue"
@change="handleObjValueChange"
:multiple="dialogTitle === '添加列配置'"
placeholder="请选择"
maxlength="30"
@ -235,7 +244,7 @@ const { metasecurity_ctr, metasecurity_obj, metasecurity_type} = proxy.useDict("
const queryParams = ref({
dbCName: '',
dbTName: '',
objValue: '',
objName: '',
pageNum: 1,
pageSize: 10
});
@ -278,9 +287,15 @@ const form = ref({
ctrlType: '',
objType: '',
objValue: '',
objName: '',
isStop: false
});
const ids = ref([]);
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
const loading = ref(true);
const tableData = ref([]);
const total = ref(0);
@ -293,6 +308,23 @@ const formRules = {
ctrlType: [{ required: true, message: '控制类型不能为空', trigger: 'change' }]
};
const dbResourceOldList = ref([]);
const handleObjValueChange = (selectedValues) => {
//
const sourceList = form.value.objType === '0'
? userList.value
: roleList.value;
//
const names = selectedValues.map(value => {
const item = sourceList.find(item => item.id === value);
return item ? item.name : '';
});
//
form.value.objName = names.join(', ');
};
//

45
vue-fastapi-frontend/src/views/meta/metasercurity/MetaSecurityRow.vue

@ -38,19 +38,21 @@
<!-- 搜索框 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" >
<el-form-item label="模式名" prop="dbSName">
<el-input v-model="queryParams.dbSName" placeholder="请输入模式名称" clearable />
<el-input v-model="queryParams.dbSName" placeholder="请输入模式名称" clearable style="width: 220px"/>
</el-form-item>
<el-form-item label="表名" prop="dbTName">
<el-input v-model="queryParams.dbTName" placeholder="请输入行名称" clearable />
<el-input v-model="queryParams.dbTName" placeholder="请输入表名" clearable style="width: 220px"/>
</el-form-item>
<el-form-item label="字段名" prop="dbCName">
<el-input v-model="queryParams.dbCName" placeholder="请输入行名称" clearable />
<el-input v-model="queryParams.dbCName" placeholder="请输入字段名" clearable style="width: 220px"/>
</el-form-item>
<el-form-item label="用户角色" prop="objValue">
<el-input v-model="queryParams.objValue" placeholder="请输入行名称" clearable />
<el-form-item label="对象编号" prop="objValue">
<el-input v-model="queryParams.objName" placeholder="请输入对象编号" clearable style="width: 220px"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
@ -124,7 +126,13 @@
</span>
</template>
</el-table-column>
<el-table-column label="是否停用" align="center" prop="isStop" />
<el-table-column label="是否停用" align="center" prop="isStop" >
<template #default="scope">
<span >
{{ scope.row.isStop?"是":"否" }}
</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="180">
<template #default="scope">
<el-button @click="handleEdit(scope.row)" type="text" icon="Edit" />
@ -202,6 +210,7 @@
<el-select
v-model="form.objValue"
placeholder="请选择"
@change="handleObjValueChange"
:multiple="dialogTitle === '添加行配置'"
maxlength="30"
>
@ -266,8 +275,32 @@ const form = ref({
ctrlCol: '', //
objType: '', //
objValue: '', //
objName: '', //
isStop: false //
});
const handleObjValueChange = (selectedValues) => {
//
const sourceList = form.value.objType === '0'
? userList.value
: roleList.value;
//
const names = selectedValues.map(value => {
const item = sourceList.find(item => item.id === value);
return item ? item.name : '';
});
//
form.value.objName = names.join(', ');
};
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef");
handleQuery();
}
const ids = ref([]);
const loading = ref(true);
const tableData = ref([]);

Loading…
Cancel
Save