You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							754 lines
						
					
					
						
							35 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							754 lines
						
					
					
						
							35 KiB
						
					
					
				| from fastapi import Request | |
| from sqlalchemy.ext.asyncio import AsyncSession | |
| 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,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 | |
| from config.env import AppConfig | |
| import requests | |
| from sqlalchemy.exc import OperationalError | |
| import json | |
| import re | |
| from decimal import Decimal | |
| 
 | |
| class MetaSecurityService: | |
|     """ | |
|     数据源安全管理模块服务层 | |
|     """ | |
| 
 | |
|     @classmethod | |
|     async def get_meta_security_col_list_services( | |
|         cls, query_db: AsyncSession, query_object: MetaSecurityColModel, is_page: bool = False | |
|     ): | |
|         """ | |
|         获取列配置列表信息service | |
|  | |
|         :param query_db: orm对象 | |
|         :param query_object: 查询参数对象 | |
|         :param is_page: 是否开启分页 | |
|         :return: 列配置列表信息对象 | |
|         """ | |
|         col_list_result = await MetaSecurityDao.get_meta_security_col_list(query_db, query_object, is_page) | |
|         return col_list_result | |
| 
 | |
|     @classmethod | |
|     async def get_meta_security_row_list_services( | |
|         cls, query_db: AsyncSession, query_object: MetaSecurityRowModel, is_page: bool = False | |
|     ): | |
|         """ | |
|         获取行配置列表信息service | |
|  | |
|         :param query_db: orm对象 | |
|         :param query_object: 查询参数对象 | |
|         :param is_page: 是否开启分页 | |
|         :return: 行配置列表信息对象 | |
|         """ | |
|         row_list_result = await MetaSecurityDao.get_meta_security_row_list(query_db, query_object, is_page) | |
|         return row_list_result | |
| 
 | |
|     @classmethod | |
|     async def get_meta_security_col_by_id_services(cls, query_db: AsyncSession, colId: str): | |
|         """ | |
|         获取列配置详细信息service | |
|  | |
|         :param query_db: orm对象 | |
|         :param colId: 列配置ID | |
|         :return: 列配置详细信息对象 | |
|         """ | |
|         col = await MetaSecurityDao.get_meta_security_col_by_id(query_db, colId) | |
|         if col: | |
|             result = MetaSecurityColModel(**CamelCaseUtil.transform_result(col)) | |
|         else: | |
|             result = MetaSecurityColModel(**dict()) | |
|         return result | |
| 
 | |
|     @classmethod | |
|     async def get_meta_security_row_by_id_services(cls, query_db: AsyncSession, rowId: str): | |
|         """ | |
|         获取行配置详细信息service | |
|  | |
|         :param query_db: orm对象 | |
|         :param rowId: 行配置ID | |
|         :return: 行配置详细信息对象 | |
|         """ | |
|         row = await MetaSecurityDao.get_meta_security_row_by_id(query_db, rowId) | |
|         if row: | |
|             result = MetaSecurityRowModel(**CamelCaseUtil.transform_result(row)) | |
|         else: | |
|             result = MetaSecurityRowModel(**dict()) | |
|         return result | |
| 
 | |
|     @classmethod | |
|     async def add_meta_security_col_services(cls, request: Request, query_db: AsyncSession, page_object: MetaSecurityColModel): | |
|         """ | |
|         新增列配置服务 | |
|  | |
|         :param request: Request对象 | |
|         :param query_db: orm对象 | |
|         :param page_object: 新增的列配置对象 | |
|         :return: 新增列配置校验结果 | |
|         """ | |
|         try: | |
|             if isinstance(page_object.obj_value, str) and page_object.obj_value: | |
|                 obj_values = page_object.obj_value.split(",") | |
|                 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) | |
|             await query_db.commit() | |
|             return CrudResponseModel(is_success=True, message='新增列配置成功') | |
|         except Exception as e: | |
|             await query_db.rollback() | |
|             raise e | |
| 
 | |
|     @classmethod | |
|     async def add_meta_security_row_services(cls, request: Request, query_db: AsyncSession, page_object: MetaSecurityRowModel): | |
|         """ | |
|         新增行配置服务 | |
|  | |
|         :param request: Request对象 | |
|         :param query_db: orm对象 | |
|         :param page_object: 新增的行配置对象 | |
|         :return: 新增行配置校验结果 | |
|         """ | |
|         try: | |
|             if isinstance(page_object.obj_value, str) and page_object.obj_value: | |
|                 obj_values = page_object.obj_value.split(",") | |
|                 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) | |
|             await query_db.commit() | |
| 
 | |
|             # 缓存相关操作,如果需要 | |
|             # await request.app.state.redis.set(...) | |
| 
 | |
|             return CrudResponseModel(is_success=True, message='新增行配置成功') | |
| 
 | |
|         except Exception as e: | |
|             await query_db.rollback() | |
|             raise e | |
|     @classmethod | |
|     async def col_detail_services(cls, query_db: AsyncSession, col: str): | |
|         """ | |
|         获取参数配置详细信息service | |
|  | |
|         :param query_db: orm对象 | |
|         :param config_id: 参数配置id | |
|         :return: 参数配置id对应的信息 | |
|         """ | |
|         config = await MetaSecurityDao.get_meta_security_col_by_id(query_db, col) | |
|         if config: | |
|             result = MetaSecurityColModel(**CamelCaseUtil.transform_result(config)) | |
|         else: | |
|             result = MetaSecurityColModel(**dict()) | |
| 
 | |
|         return result | |
|     @classmethod | |
|     async def row_detail_services(cls, query_db: AsyncSession, row_id: str): | |
|         """ | |
|         获取参数配置详细信息service | |
|  | |
|         :param query_db: orm对象 | |
|         :param config_id: 参数配置id | |
|         :return: 参数配置id对应的信息 | |
|         """ | |
|         config = await MetaSecurityDao.get_meta_security_row_by_id(query_db, row_id) | |
|         if config: | |
|             result = MetaSecurityRowModel(**CamelCaseUtil.transform_result(config)) | |
|         else: | |
|             result = MetaSecurityRowModel(**dict()) | |
| 
 | |
|         return result | |
|     @classmethod | |
|     async def edit_meta_security_col_services(cls, request: Request, query_db: AsyncSession, page_object: MetaSecurityColModel): | |
|         """ | |
|         编辑列配置服务 | |
|  | |
|         :param request: Request对象 | |
|         :param query_db: orm对象 | |
|         :param page_object: 编辑的列配置对象 | |
|         :return: 编辑列配置校验结果 | |
|         """ | |
|         edit_col = page_object.model_dump(exclude_unset=True) | |
|         col_info = await cls.get_meta_security_col_by_id_services(query_db, page_object.colId) | |
| 
 | |
|         if col_info: | |
|             try: | |
|                 await MetaSecurityDao.update_meta_security_col(query_db, edit_col) | |
|                 await query_db.commit() | |
| 
 | |
|                 # 缓存更新,如果需要 | |
|                 # await request.app.state.redis.set(...) | |
| 
 | |
|                 return CrudResponseModel(is_success=True, message='编辑列配置成功') | |
|             except Exception as e: | |
|                 await query_db.rollback() | |
|                 raise e | |
|         else: | |
|             raise ServiceException(message=f'列配置{page_object.colId}不存在') | |
| 
 | |
|     @classmethod | |
|     async def edit_meta_security_row_services(cls, request: Request, query_db: AsyncSession, page_object: MetaSecurityRowModel): | |
|         """ | |
|         编辑行配置服务 | |
|         :param request: Request对象 | |
|         :param query_db: orm对象 | |
|         :param page_object: 编辑的行配置对象 | |
|         :return: 编辑行配置校验结果 | |
|         """ | |
|         edit_row = page_object.model_dump(exclude_unset=True) | |
|         row_info = await cls.get_meta_security_row_by_id_services(query_db, page_object.rowId) | |
|         if row_info: | |
|             try: | |
|                 await MetaSecurityDao.update_meta_security_row(query_db, edit_row) | |
|                 await query_db.commit() | |
|                 return CrudResponseModel(is_success=True, message='编辑行配置成功') | |
|             except Exception as e: | |
|                 await query_db.rollback() | |
|                 raise e | |
|         else: | |
|             raise ServiceException(message=f'行配置{page_object.rowId}不存在') | |
| 
 | |
|     @classmethod | |
|     async def delete_meta_security_col_services(cls, request: Request, query_db: AsyncSession, page_object: DeleteMetaSecurityModel): | |
|         """ | |
|         删除列配置服务 | |
|         :param request: Request对象 | |
|         :param query_db: orm对象 | |
|         :param page_object: 删除列配置对象 | |
|         :return: 删除列配置校验结果 | |
|         """ | |
|         if page_object.metaSecurity_ids: | |
|             col_id_list = page_object.metaSecurity_ids.split(',') | |
|             try: | |
|                 for col_id in col_id_list: | |
|                     col_info = await cls.get_meta_security_col_by_id_services(query_db, col_id) | |
|                     if col_info: | |
|                         # 校验不能删除的系统内置列 | |
|                         await MetaSecurityDao.delete_meta_security_col(query_db, col_id) | |
|                 await query_db.commit() | |
| 
 | |
| 
 | |
|                 return CrudResponseModel(is_success=True, message='删除列配置成功') | |
|             except Exception as e: | |
|                 await query_db.rollback() | |
|                 raise e | |
|         else: | |
|             raise ServiceException(message='传入列配置ID为空') | |
|          | |
|     @classmethod | |
|     async def delete_meta_security_row_services(cls, request: Request, query_db: AsyncSession, page_object: DeleteMetaSecurityModel): | |
|         """ | |
|         删除行配置服务 | |
|  | |
|         :param request: Request对象 | |
|         :param query_db: orm对象 | |
|         :param page_object: 删除行配置对象 | |
|         :return: 删除行配置校验结果 | |
|         """ | |
|         if page_object.metaSecurity_ids: | |
|             row_id_list = page_object.metaSecurity_ids.split(',') | |
|             try: | |
|                 for row_id in row_id_list: | |
|                     row_info = await cls.get_meta_security_row_by_id_services(query_db, row_id) | |
|                     if row_info:                | |
|                         await MetaSecurityDao.delete_meta_security_row(query_db, row_id) | |
|                 await query_db.commit() | |
|                 return CrudResponseModel(is_success=True, message='删除行配置成功') | |
|             except Exception as e: | |
|                 await query_db.rollback() | |
|                 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='用户密码错误!') | |
|         forbidden_keywords = ["UPDATE", "DELETE", "INSERT", "DROP", "ALTER", "TRUNCATE"] | |
|         pattern = re.compile(r"\b(" + "|".join(forbidden_keywords) + r")\b", re.IGNORECASE) | |
|         if pattern.search(page_object.sqlStr): | |
|             raise ServiceException(data='', message='SQL 中包含敏感词(UPDATE, DELETE, INSERT, DROP, ALTER, TRUNCATE),禁止执行!') | |
|         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.测试数据源连接是否正常 | |
|         # mysql | |
|         # 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"} | |
|         # postgresql | |
|         # dataParams ={"user":"testuser","password":"testpd","address":"jdbc:postgresql://47.121.207.11:5432","database":"zx2","jdbcUrl":"jdbc:postgresql://47.121.207.11:5432/zx2","driverClassName":"org.postgresql.Driver","validationQuery":"select version()"} | |
|         dsDataResource=await get_data_source_tree(request,page_object) | |
|         # dbConnent= cls.get_db_engine("postgresql",dataParams]) | |
|         dbConnent= cls.get_db_engine(dsDataResource["type"],dsDataResource["connectionParams"]) | |
|         # await test_connection(dbConnent) | |
|         #3获取sql中涉及的表名 | |
|         sqlScheamAndTable =await cls.get_tables_from_sql(page_object.sqlStr) | |
|         oldStrSql=generate_pagination_sql(page_object,dsDataResource["type"]) | |
|         #4.执行原始sql | |
|         result = await cls.execute_sql(dbConnent, oldStrSql,"原始") | |
|         if 3 in role_id_list: | |
|                 resultDict={ | |
|                 "ctrlSql": page_object.sqlStr, | |
|                 "data":  result, | |
|                 "message":"数据安全管理员权限" | |
|                 } | |
|                 return resultDict  | |
|          | |
|      | |
|         #5.根据表名获取数据库中的字段名 | |
|         table_columns = await cls.get_columns_from_tables(dbConnent, sqlScheamAndTable,dsDataResource["type"],) | |
|          | |
|         #6.查询用户及该用户角色下的所有行列配置 | |
|         tablesRowCol = {} | |
| 
 | |
|         # 遍历每个表名,获取对应的配置 | |
|         for table_name in sqlScheamAndTable: | |
|             table_configs = await get_table_configs(query_db, page_object, user, role_id_list, table_name) | |
|             tablesRowCol[table_name] = table_configs | |
|          | |
|         # 返回最终的结果字典 | |
|         ctrSqlDict = await generate_sql(tablesRowCol,table_columns) | |
|         oldStrSql= page_object.sqlStr | |
|         if page_object.isPage: | |
|             oldStrSql=generate_pagination_sql(page_object,dsDataResource["type"]) | |
|         #7.根据行列配置控制原始sql | |
|         newStrSql =await replace_table_with_subquery(ctrSqlDict,oldStrSql) | |
|         #8.执行结果 | |
|         result = await cls.execute_sql(dbConnent, newStrSql,"控制后") | |
|         resultDict={ | |
|          "ctrlSql": newStrSql, | |
|          "data":  result, | |
|          "tablesRowCol":tablesRowCol | |
|         } | |
|         return resultDict | |
| 
 | |
|    | |
| 
 | |
|     def get_db_engine(db_type: str, db_params: dict): | |
|         try:   | |
|             address = db_params['address'] | |
|             jdbc_prefixes = { | |
|                 "jdbc:mysql://": len("jdbc:mysql://"), | |
|                 "jdbc:postgresql://": len("jdbc:postgresql://") | |
|             } | |
|              | |
|             # Check and remove the matching prefix | |
|             for prefix, length in jdbc_prefixes.items(): | |
|                 if address.startswith(prefix): | |
|                     address = address[length:] | |
|                     db_params['address']=address | |
|                     break  # Once the correct prefix is found, exit the loop | |
|             if db_type.lower() == "mysql": | |
|                 conn_str=f"mysql+aiomysql://{db_params['user']}:{db_params['password']}@{db_params['address']}/{db_params['database']}" | |
|                 print(f"数据库连接字符串: {conn_str}")  # 输出调试信息 | |
|                 dbContent=  create_async_engine(conn_str) | |
|                 return  dbContent | |
|             elif db_type.lower() == "postgresql": | |
|                 dbContent= create_async_engine(f"postgresql+asyncpg://{db_params['user']}:{db_params['password']}@{db_params['address']}/{db_params['database']}") | |
|                 return  dbContent | |
|             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] | |
|                     # # 使用 convert_decimal 处理数据 | |
|                     # result_dict = [convert_decimal(row) for row in result_dict] | |
|                     # 转换为 JSON 字符串 | |
|                     return result_dict | |
|                 except SQLAlchemyError as e: | |
|                     raise RuntimeError(f"{sql_type}执行 SQL 查询时发生错误: {e}")         | |
| 
 | |
|     # async def get_tables_from_sql(sql_query: str): | |
|     #    """ | |
|     #    解析 SQL 查询,提取所有 Schema 和 Table 名称,并确保表名包含模式名(schema.table)。 | |
|         | |
|     #    :param sql_query: SQL 查询字符串 | |
|     #    :return: {'schemas': [...], 'table_names': [...]} | |
|     #    :raises ServiceException: 如果 SQL 未使用 schema.table 结构,则抛出异常 | |
|     #    """ | |
|     #    # ✅ 改进正则:支持 `FROM a.o, b.x JOIN c.y` | |
|     #    table_section_pattern = r"(?i)(?:FROM|JOIN|INTO|UPDATE)\s+([\w\.\s,]+)" | |
|         | |
|     #    table_sections = re.findall(table_section_pattern, sql_query, re.DOTALL) | |
|         | |
|     #    if not table_sections: | |
|     #        raise ServiceException(data='', message='SQL 解析失败,未找到表名')     | |
|     #    # 解析多个表(用 `,` 和 `JOIN` 拆分) | |
|     #    for section in table_sections: | |
|     #        tables = re.split(r"\s*,\s*|\s+JOIN\s+", section, flags=re.IGNORECASE) | |
|     #        for table in tables: | |
|     #            table = table.strip().split()[0]  # 取 `schema.table`,忽略别名 | |
|     #            if "." not in table: | |
|     #                raise ServiceException( | |
|     #                    data='', | |
|     #                    message=f"SQL 中的表名必须携带模式名(schema.table),但发现了无模式的表:{table}" | |
|     #                ) | |
|     #    return table_sections | |
|     async def get_tables_from_sql(sql_query: str): | |
|       """ | |
|       解析 SQL 查询,提取所有 Schema 和 Table 名称,并确保表名包含模式名(schema.table)。 | |
|        | |
|       :param sql_query: SQL 查询字符串 | |
|       :return: {'schemas': [...], 'table_names': [...]} | |
|       :raises ServiceException: 如果 SQL 未使用 schema.table 结构,则抛出异常 | |
|       """ | |
|       # ✅ 改进正则,支持 `FROM ... JOIN ...`,并适配换行符 | |
|       table_section_pattern = r"(?i)(?:FROM|JOIN|INTO|UPDATE)\s+([\w\.\s,]+)" | |
| 
 | |
|       # 允许 `.` 匹配换行符,确保提取完整的 `FROM` 和 `JOIN` 语句 | |
|       table_sections = re.findall(table_section_pattern, sql_query, re.DOTALL) | |
| 
 | |
|       if not table_sections: | |
|           raise ServiceException(data='', message='SQL 解析失败,未找到表名') | |
| 
 | |
|       table_names = set()  # 使用集合去重 | |
|       for section in table_sections: | |
|           # 按 `,` 或 `JOIN` 拆分,提取表名 | |
|           tables = re.split(r"\s*,\s*|\s+JOIN\s+", section.strip(), flags=re.IGNORECASE) | |
|           for table in tables: | |
|               table = table.strip().split()[0]  # 取 `schema.table`,忽略别名 | |
|               if "." not in table: | |
|                   raise ServiceException( | |
|                       data='', | |
|                       message=f"SQL 中的表名必须携带模式名(schema.table),但发现了无模式的表:{table}" | |
|                   ) | |
|               table_names.add(table) | |
| 
 | |
|       return list(table_names) | |
| 
 | |
|     @classmethod | |
|     async def get_columns_from_tables(cls, dbConnent, table_names, db_type: str): | |
|         """查询每个表的字段信息,根据数据库类型调整查询""" | |
|         columns = {} | |
|         query="" | |
|         for table_name in table_names: | |
|             schema, table = table_name.split(".") | |
|             if db_type.lower() == "postgresql": | |
|                 # PostgreSQL: 查询指定 schema 下的表字段 | |
|                 query = f""" | |
|                     SELECT column_name  | |
|                     FROM information_schema.columns  | |
|                     WHERE table_schema = '{schema}' AND table_name = '{table}' | |
|                 """ | |
|             elif db_type.lower() == "mysql": | |
|                 # MySQL: 直接查询表字段(MySQL 没有 schema 的概念) | |
|                 query = f""" | |
|                     SELECT COLUMN_NAME  | |
|                     FROM INFORMATION_SCHEMA.COLUMNS  | |
|                     WHERE TABLE_NAME = '{table}' | |
|                 """ | |
|             else: | |
|                 raise ValueError(f"暂不支持数据库类型: {db_type}") | |
|      | |
|             # Execute the query for the specific table | |
|             result = await cls.execute_sql(dbConnent, query, "字段查询") | |
|              | |
|             # 将结果转换为字典格式 {table_name: ['column1', 'column2', ...]} | |
|             columns[table_name] = [row["column_name"] for row in result] | |
|      | |
|         return columns | |
|      | |
| def convert_decimal(obj): | |
|     if isinstance(obj, Decimal): | |
|         return float(obj)  # 或者 str(obj) 来保留精度 | |
|     elif isinstance(obj, dict): | |
|         # 递归处理字典中的每个值 | |
|         return {key: convert_decimal(value) for key, value in obj.items()} | |
|     elif isinstance(obj, list): | |
|         # 递归处理列表中的每个元素 | |
|         return [convert_decimal(item) for item in obj] | |
|     return obj  # 返回非 Decimal、dict 或 list 的值 | |
| async def get_table_configs(query_db, page_object, user, role_id_list, table_name): | |
|     parts = table_name.split(".") | |
|     schema, table = parts | |
|     # 获取用户的列配置 | |
|     user_col_list = await MetaSecurityDao.get_api_col_list( | |
|         query_db, page_object.dbRCode, schema,table, '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.dbRCode, schema,table, '1', role_id | |
|         ) | |
|         role_col_list.extend(role_cols)  # 将每个角色的列配置合并到列表中 | |
| 
 | |
|     # 获取用户的行配置 | |
|     user_row_list = await MetaSecurityDao.get_api_row_list( | |
|         query_db, page_object.dbRCode, schema,table, '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.dbRCode,schema,table, '1', role_id | |
|         ) | |
|         role_row_list.extend(role_rows)  # 将每个角色的行配置合并到列表中 | |
|     isHave = any([ | |
|         user_col_list, | |
|         role_col_list, | |
|         user_row_list, | |
|         role_row_list | |
|     ])     | |
|     return { | |
|         "user_col_list": user_col_list, | |
|         "role_col_list": role_col_list, | |
|         "role_row_list": role_row_list, | |
|         "user_row_list": user_row_list, | |
|         "isHave":isHave | |
|     } | |
| async def generate_sql(tablesRowCol:dict, table_columns:dict): | |
|     sql_queries = {} | |
|        | |
|     # 1. 列控制 | |
|     # 遍历每个表  | |
|     isHave=False | |
|     no_configTable_name="" | |
|     for table_name, table_configs in tablesRowCol.items(): | |
|         if table_configs["isHave"]: | |
|             isHave=True | |
|         else: | |
|             no_configTable_name += table_name + "," | |
|     if not isHave: | |
|         no_configTable_name = no_configTable_name.rstrip(',') | |
|         raise  ValueError(f"表:{no_configTable_name}均未配置行列数据安全")          | |
|     for table_name, config in tablesRowCol.items(): | |
|         # 获取该表的字段名 | |
|         columns = {col.lower(): col for col in table_columns[table_name]}  # 将字段名转为小写 | |
|         # 初始化 SELECT 部分:用字典存储字段名,值是 null 字段名 | |
|         select_columns = {col: f"null {col}" for col in columns} | |
| 
 | |
|         # 处理角色列配置 | |
|         for col in config["role_col_list"]: | |
|             # If dbCName is "ALL", handle it as a special case | |
|             if col.dbCName == "ALL": | |
|                 if col.ctrl_type == '0':  # If ctrl_type is '0', prefix all columns with null | |
|                     for db_column in columns:  # Assuming 'user' is the table name | |
|                         select_columns[db_column] = f"null {db_column}"  # 仍然保留 null 前缀 | |
|                 elif col.ctrl_type == '1':  # If ctrl_type is '1', use actual column names | |
|                     for db_column in columns: | |
|                         select_columns[db_column] = db_column  # 使用实际字段名 | |
|             else: | |
|                 # Handle specific columns listed in dbCName | |
|                 db_columns = [db_column.strip().lower() for db_column in col.dbCName.split(",")] | |
|                 for db_column in db_columns: | |
|                     db_column = db_column.strip() | |
|                     if db_column in columns:  # Check if the column exists in the table | |
|                         if col.ctrl_type == '0':  # If ctrl_type is '0', prefix with null | |
|                             select_columns[db_column] = f"null {db_column}"  # 仍然保留 null 前缀 | |
|                         elif col.ctrl_type == '1':  # If ctrl_type is '1', use actual column name | |
|                             select_columns[db_column] = db_column  # 使用实际字段名 | |
|         # 处理用户列配置 | |
|         for col in config["user_col_list"]: | |
|             if col.dbCName == "ALL":  # 如果 dbCName 为 "ALL" | |
|                 if col.ctrl_type == "0":  # ctrlType 为 0,字符串字段 | |
|                     for db_column in columns:  # 对所有字段加上 null | |
|                             select_columns[db_column] = f"null {db_column}"  # 仍然保留 null 前缀 | |
|                 elif col.ctrl_type == "1":  # ctrlType 为 1,实际数据库字段 | |
|                     for db_column in columns:  # 使用实际字段名,不加 null | |
|                             select_columns[db_column] = db_column  # 使用实际字段名 | |
|             else:  # 处理 dbCName 不为 "ALL" 的情况 | |
|                 db_columns = [db_column.strip().lower() for db_column in col.dbCName.split(",")] | |
|                 for db_column in db_columns: | |
|                     db_column = db_column.strip() | |
|                     if db_column in columns: | |
|                         if col.ctrl_type == "0": | |
|                             select_columns[db_column] = f"null {db_column}"  # 仍然保留 null 前缀 | |
|                         elif col.ctrl_type == "1": | |
|                             select_columns[db_column] = db_column  # 使用实际字段名 | |
|         # 生成 SQL 查询 | |
|         sql_queries[table_name] = f"SELECT {', '.join(select_columns.values())} FROM {table_name}" | |
|         # 2.行控制 | |
|         select_rows={} | |
|         # 处理角色行配置 | |
|         for row in config["role_row_list"]: | |
|             # 仅仅对固定值有效,不加行限制 | |
|             if row.ctrl_value == "ALL" and row.ctrl_type == '0': | |
|                 # 控制方式 --固定值 | |
|                 select_rows[row.dbCName] = "" | |
|             else: | |
|                 if row.ctrl_type == '0': | |
|                     # row.ctrl_value 是逗号分隔的字符串时,改为 IN 语句 | |
|                     if "," in row.ctrl_value: | |
|                         # 将 ctrl_value 按逗号分割,并用单引号包裹每个值 | |
|                         values = [f"'{value.strip()}'" for value in row.ctrl_value.split(",")] | |
|                         select_rows[row.dbCName] = f"{row.dbCName} IN ({', '.join(values)})" | |
|                     else: | |
|                         select_rows[row.dbCName] = f"{row.dbCName} = '{row.ctrl_value}'" | |
|                 if row.ctrl_type == '1': | |
|                     tab_col_value=row.ctrl_value.split(".") | |
|                     if len(tab_col_value) != 2: | |
|                         raise RuntimeError(f"{row.dbCName}字段控制类型为表字段,未维护正确的值")     | |
|                     select_rows[row.dbCName] = f"{row.dbCName} in (select {tab_col_value[1]} from {row.dbSName}.{tab_col_value[0]} where user_id = '1')" | |
|         # 处理用户行配置 | |
|         for row in config["user_row_list"]: | |
|             # 仅仅对固定值有效,不加行限制 | |
|             if row.ctrl_value == "ALL" and row.ctrl_type == '0': | |
|                 # 控制方式 --固定值 | |
|                 select_rows[row.dbCName] = "" | |
|             else: | |
|                 if row.ctrl_type == '0': | |
|                     # row.obj_value 是逗号分隔的字符串时,改为 IN 语句 | |
|                     if "," in row.ctrl_value: | |
|                         # 将 obj_value 按逗号分割,并用单引号包裹每个值 | |
|                         values = [f"'{value.strip()}'" for value in row.ctrl_value.split(",")] | |
|                         select_rows[row.dbCName] = f"{row.dbCName} IN ({', '.join(values)})" | |
|                     else: | |
|                         select_rows[row.dbCName] = f"{row.dbCName} = '{row.ctrl_value}'" | |
|                 if row.ctrl_type == '1': | |
|                     tab_col_value=row.ctrl_value.split(".") | |
|                     if len(tab_col_value) != 2: | |
|                         raise RuntimeError(f"{row.dbCName}字段控制类型为表字段,未维护正确的值")     | |
|                     select_rows[row.dbCName] = f"{row.dbCName} in (select {tab_col_value[1]} from {row.dbSName}.{tab_col_value[0]} where user_id = '1')" | |
|         if select_rows.values(): | |
|             where_conditions = " AND ".join(select_rows.values()) | |
|             if where_conditions: | |
|                sql_queries[table_name] += " WHERE " + where_conditions | |
|     return sql_queries | |
| async def replace_table_with_subquery(ctrSqlDict, oldStrSql): | |
|     table_alias_map = {} # 存储表名和别名的映射 | |
|     for table_name, subquery in ctrSqlDict.items(): | |
|         # 构建正则表达式,匹配表名及可能的别名 | |
|         pattern = ( | |
|             r'(\b(?:[a-zA-Z_][a-zA-Z0-9_]*\.)?'  # 匹配模式名(可选) | |
|             + re.escape(table_name)              # 转义表名 | |
|             + r'\b)'                             # 结束表名 | |
|             r'(\s+(?:AS\s+)?(\w+))?'             # 捕获别名部分(含 AS 或直接别名) | |
|             r'(?=\s*[\w\(\)]*)'                  # 确保后面是合法 SQL 语法,不是 SQL 关键字 | |
|         ) | |
|         def replace(match): | |
|             original_table = match.group(1)  # 原始表名(可能含模式名) | |
|             alias_part = match.group(2)      # 别名部分(含空格、AS 或直接别名) | |
|             alias_name = match.group(3)      # 别名名称(无 AS 前缀) | |
|             if original_table not in table_alias_map: | |
|                 # 处理表名后直接跟着 SQL 关键字的情况 | |
|                 sql_keywords = {"LIMIT", "WHERE", "ORDER", "GROUP", "HAVING", "JOIN", "ON", "USING", "UNION", | |
|                                 "EXCEPT", "INTERSECT", "FETCH", "OFFSET"} | |
|                 if alias_name and alias_name.upper().split()[0] not in sql_keywords: | |
|                     # 已存在别名,且别名后没有紧跟 SQL 关键字,保留原别名 | |
|                     replaced = f"({subquery}) {alias_part}" | |
|                     table_alias_map[original_table] = alias_part | |
|                 else: | |
|                     # 无别名时,或者别名无效(如 LIMIT),添加默认别名 | |
|                     alias = original_table.split('.')[-1] | |
|                     replaced = f"({subquery}) AS {alias}{alias_part}" | |
|                     table_alias_map[original_table] = alias | |
|             else: | |
|                     alias = table_alias_map[original_table] | |
|                     replaced = f"{alias}"  # 使用别名       | |
|             return replaced | |
|         # 执行替换(忽略大小写) | |
|         oldStrSql = re.sub(pattern, replace, oldStrSql, flags=re.IGNORECASE) | |
|      | |
|     return oldStrSql | |
| 
 | |
| 
 | |
| 
 | |
| async def get_data_source_tree(request: Request, current_user: MetaSecurityApiModel): | |
|      | |
|    url = f'{AppConfig.ds_server_url}/dolphinscheduler/datasources/withpwdlist?pageNo=1&pageSize=100' | |
|    headers = {'dashUserName': current_user.username ,'dashPassword': current_user.password,} | |
|    response = requests.get(url, headers=headers,  verify=False) | |
|    if response.reason == 'OK': | |
|        response_text = response.text | |
|        data = json.loads(response_text) | |
|        total_list = data["data"]["totalList"] | |
|        # 解析 connectionParams 字符串为字典 | |
|        for item in total_list: | |
|            if item["name"]==current_user.dbRCode: | |
|                item["connectionParams"] = json.loads(item["connectionParams"]) | |
|                return item | |
|        raise Exception(f'根据数据源ID:{current_user.dbRCode}获取数据源信息失败,状态: {response.reason}') | |
|     | |
|    else: | |
|         raise Exception(f'根据数据源ID:{current_user.dbRCode}获取数据源信息失败,状态: {response.reason}') | |
| async def test_connection(db_content): | |
|     try: | |
|         # 尝试执行一个简单的查询来测试连接 | |
|         async with db_content.connect() as connection: | |
|             # 这里执行一个简单的查询,例如“SELECT 1” | |
|             await connection.scalar("SELECT 1") | |
|     except Exception as e: | |
|         raise Exception("数据源连接失败") from e | |
| def generate_pagination_sql(page_object: MetaSecurityApiModel, db_type: str) -> str: | |
|     """ | |
|     生成带分页的 SQL 语句 | |
|  | |
|     :param page_object: 包含分页参数的对象 | |
|     :param db_type: 数据库类型(大写字符串) | |
|     :return: 带分页的 SQL 语句 | |
|     """ | |
|     page_num = page_object.pageNum or 1  # 当前页码,默认为 1 | |
|     page_size = page_object.pageSize or 10  # 每页大小,默认为 10 | |
|     offset = (page_num - 1) * page_size  # 计算偏移量(跳过的行数) | |
|     oldStrSql = page_object.sqlStr  # 获取原始 SQL 语句 | |
| 
 | |
|     db_type = db_type.upper()  # 确保数据库类型为大写 | |
| 
 | |
|     if db_type == "MYSQL" or db_type == "POSTGRESQL": | |
|         newStrSql = f"{oldStrSql} LIMIT {page_size} OFFSET {offset}" | |
|     elif db_type == "SQLSERVER": | |
|         newStrSql = f"{oldStrSql} ORDER BY id OFFSET {offset} ROWS FETCH NEXT {page_size} ROWS ONLY" | |
|     elif db_type == "ORACLE": | |
|         newStrSql = f""" | |
|         SELECT * FROM ( | |
|             SELECT a.*, ROWNUM rnum FROM ( | |
|                 {oldStrSql} ORDER BY id | |
|             ) a WHERE ROWNUM <= {offset + page_size} | |
|         ) WHERE rnum > {offset} | |
|         """ | |
|     else: | |
|         raise ValueError(f"不支持的数据库类型: {db_type}") | |
| 
 | |
|     return newStrSql     |