diff --git a/vue-fastapi-backend/module_admin/controller/data_asset_controller.py b/vue-fastapi-backend/module_admin/controller/data_asset_controller.py new file mode 100644 index 0000000..daedc26 --- /dev/null +++ b/vue-fastapi-backend/module_admin/controller/data_asset_controller.py @@ -0,0 +1,147 @@ +from fastapi import APIRouter, Depends, Form, Request +from sqlalchemy.ext.asyncio import AsyncSession +from config.get_db import get_db +from module_admin.aspect.interface_auth import CheckUserInterfaceAuth +from module_admin.service.login_service import LoginService +from module_admin.service.data_asset_service import DataAssetService +from module_admin.entity.vo.data_asset_vo import DataAssetBatchModel, DataAssetPageQueryModel, DataAssetResultModel, DataAssetSearchModel +from utils.log_util import logger +from utils.page_util import PageResponseModel +from utils.response_util import ResponseUtil +from typing import List +from config.enums import BusinessType +from utils.common_util import bytes2file_response +from module_admin.annotation.log_annotation import Log + +dataAssetController = APIRouter(prefix='/system/dataAsset') + + +@dataAssetController.get('/list', response_model=PageResponseModel) +async def get_data_asset_list( + request: Request, + data_asset_page_query: DataAssetPageQueryModel = Depends(DataAssetPageQueryModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + """ + 获取数据资产信息列表 + + :param request: 请求对象 + :param data_asset_page_query: 查询参数 + :param query_db: 数据库会话 + :return: 数据资产信息列表 + """ + # 获取分页数据 + data_asset_page_query_result = await DataAssetService.get_data_asset_list_services( + query_db, data_asset_page_query, is_page=True + ) + logger.info('获取数据资产信息列表成功') + + return ResponseUtil.success(model_content=data_asset_page_query_result) + + +@dataAssetController.post('/batch') +async def batch_process_data_asset( + request: Request, + batch_data: DataAssetBatchModel, + query_db: AsyncSession = Depends(get_db), +): + """ + 批量处理数据资产信息(新增/修改/删除) + + :param request: 请求对象 + :param batch_data: 批量处理数据 + :param query_db: 数据库会话 + :return: 处理结果 + """ + try: + # 批量处理数据资产 + result = await DataAssetService.batch_process_data_asset_services(query_db, batch_data) + + # 记录处理结果 + logger.info(f'批量处理数据资产成功,成功: {result.success_count},失败: {result.failed_count}') + + # 返回处理结果 + return ResponseUtil.success(data=result) + except Exception as e: + logger.error(f'批量处理数据资产失败: {str(e)}') + return ResponseUtil.error(msg=f"处理失败: {str(e)}") + +@dataAssetController.get('/sources', response_model=List[str]) +async def get_data_asset_sources( + request: Request, + query_db: AsyncSession = Depends(get_db), +): + """ + 获取所有数据资产来源列表 + + :param request: 请求对象 + :param query_db: 数据库会话 + :return: 数据资产来源列表 + """ + sources = await DataAssetService.get_data_asset_sources_services(query_db) + logger.info('获取数据资产来源列表成功') + + return ResponseUtil.success(data=sources) + + +@dataAssetController.get('/search', response_model=PageResponseModel) +async def search_data_assets( + request: Request, + search_params: DataAssetSearchModel = Depends(DataAssetSearchModel.as_query), + query_db: AsyncSession = Depends(get_db), +): + """ + 综合查询数据资产信息 + + :param request: 请求对象 + :param search_params: 查询参数 + :param query_db: 数据库会话 + :return: 查询结果 + """ + # 将查询参数转换为字典,剔除None值 + params_dict = {k: v for k, v in search_params.model_dump().items() + if v is not None and k not in ['page_num', 'page_size']} + + # 执行查询 + search_result = await DataAssetService.search_data_assets_services( + query_db, + params_dict, + search_params.page_num, + search_params.page_size + ) + + logger.info('综合查询数据资产信息成功') + + return ResponseUtil.success(model_content=search_result) + + +# @dataAssetController.get('/search', response_model=PageResponseModel) +# async def search_data_assets( +# request: Request, +# search_params: DataAssetSearchModel = Depends(DataAssetSearchModel.as_query), +# query_db: AsyncSession = Depends(get_db), +# ): + +@dataAssetController.post("/export") +@Log(title="数据资产", business_type=BusinessType.EXPORT) +async def export_data_assets( + request: Request, + data_asset_query: DataAssetPageQueryModel = Form(), + query_db: AsyncSession = Depends(get_db), +): + """ + 导出数据资产信息 + + :param request: 请求对象 + :param data_asset_query: 查询参数 + :param query_db: 数据库会话 + :return: Excel文件流 + """ + + data_assets = await DataAssetService.get_data_asset_list_services( + query_db, data_asset_query, is_page=False + ) + excel_data = await DataAssetService.export_data_asset_list_services(data_assets) + logger.info('导出成功') + + return ResponseUtil.streaming(data=bytes2file_response(excel_data)) \ No newline at end of file diff --git a/vue-fastapi-backend/module_admin/dao/data_asset_dao.py b/vue-fastapi-backend/module_admin/dao/data_asset_dao.py new file mode 100644 index 0000000..9f096fc --- /dev/null +++ b/vue-fastapi-backend/module_admin/dao/data_asset_dao.py @@ -0,0 +1,166 @@ +from sqlalchemy import select, update, delete, insert +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.entity.do.data_ast_content_do import DataAssetInfo +from module_admin.entity.vo.data_asset_vo import DataAssetItemModel, DataAssetPageQueryModel +from utils.page_util import PageUtil +from typing import List, Dict, Any + + +class DataAssetDao: + """ + 数据资产信息模块数据库操作层 + """ + + @classmethod + async def get_data_asset_list(cls, db: AsyncSession, query_object: DataAssetPageQueryModel, is_page: bool = False): + """ + 根据查询参数获取数据资产信息列表 + + :param db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 数据资产信息列表对象 + """ + query = ( + select(DataAssetInfo) + .where( + DataAssetInfo.ast_no == query_object.ast_no if query_object.ast_no else True, + DataAssetInfo.data_ast_eng_name.like(f'%{query_object.data_ast_eng_name}%') if query_object.data_ast_eng_name else True, + DataAssetInfo.data_ast_cn_name.like(f'%{query_object.data_ast_cn_name}%') if query_object.data_ast_cn_name else True, + DataAssetInfo.data_ast_type == query_object.data_ast_type if query_object.data_ast_type else True, + DataAssetInfo.data_ast_stat == query_object.data_ast_stat if query_object.data_ast_stat else True, + DataAssetInfo.data_ast_src == query_object.data_ast_src if query_object.data_ast_src else True, + ) + .order_by(DataAssetInfo.data_ast_estb_time.desc()) + ) + + data_asset_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page) + return data_asset_list + + @classmethod + async def get_data_asset_by_ast_no(cls, db: AsyncSession, ast_no: str): + """ + 根据资产编号获取数据资产信息 + + :param db: orm对象 + :param ast_no: 资产编号 + :return: 数据资产信息对象 + """ + data_asset = ( + await db.execute(select(DataAssetInfo).where(DataAssetInfo.ast_no == ast_no)) + ).scalars().first() + return data_asset + + @classmethod + async def insert_data_asset(cls, db: AsyncSession, data_asset: Dict[str, Any]): + """ + 插入数据资产信息 + + :param db: orm对象 + :param data_asset: 数据资产信息字典 + :return: 插入的数据资产对象 + """ + db_data_asset = DataAssetInfo(**data_asset) + db.add(db_data_asset) + await db.flush() + return db_data_asset + + @classmethod + async def update_data_asset(cls, db: AsyncSession, ast_no: str, data_asset: Dict[str, Any]): + """ + 更新数据资产信息 + + :param db: orm对象 + :param ast_no: 资产编号 + :param data_asset: 数据资产信息字典 + :return: 更新结果 + """ + result = await db.execute( + update(DataAssetInfo) + .where(DataAssetInfo.ast_no == ast_no) + .values(**data_asset) + ) + return result.rowcount + + @classmethod + async def delete_data_asset(cls, db: AsyncSession, ast_no: str): + """ + 删除数据资产信息 + + :param db: orm对象 + :param ast_no: 资产编号 + :return: 删除结果 + """ + result = await db.execute( + delete(DataAssetInfo) + .where(DataAssetInfo.ast_no == ast_no) + ) + return result.rowcount + @classmethod + async def get_data_asset_sources(cls, db: AsyncSession): + """ + 获取所有数据资产来源列表 + + :param db: orm对象 + :return: 数据资产来源列表 + """ + # 使用distinct查询不重复的数据资产来源 + result = await db.execute( + select(DataAssetInfo.data_ast_src) + .distinct() + .where(DataAssetInfo.data_ast_src.is_not(None)) + .order_by(DataAssetInfo.data_ast_src) + ) + sources = result.scalars().all() + return sources + @classmethod + async def search_data_assets(cls, db: AsyncSession, search_params: dict, is_page: bool = False, page_num: int = 1, page_size: int = 10): + """ + 综合查询数据资产信息 + + :param db: orm对象 + :param search_params: 查询参数字典 + :param is_page: 是否分页 + :param page_num: 页码 + :param page_size: 每页大小 + :return: 查询结果 + """ + query = select(DataAssetInfo) + + # 构建查询条件 + conditions = [] + + # 名称查询(支持英文名称和中文名称) + if search_params.get('name'): + name_keyword = f"%{search_params['name']}%" + conditions.append( + (DataAssetInfo.data_ast_eng_name.like(name_keyword)) | + (DataAssetInfo.data_ast_cn_name.like(name_keyword)) + ) + + # 数据资产类型 + if search_params.get('data_ast_type'): + conditions.append(DataAssetInfo.data_ast_type == search_params['data_ast_type']) + + # 资产应用场景 + if search_params.get('data_ast_screen'): + conditions.append(DataAssetInfo.data_ast_screen == search_params['data_ast_screen']) + + # 数据资产标签 + if search_params.get('data_ast_clas'): + conditions.append(DataAssetInfo.data_ast_clas.like(f"%{search_params['data_ast_clas']}%")) + + # 数据资产来源 + if search_params.get('data_ast_src'): + conditions.append(DataAssetInfo.data_ast_src == search_params['data_ast_src']) + + # 将所有条件添加到查询 + for condition in conditions: + query = query.where(condition) + + # 排序 + query = query.order_by(DataAssetInfo.data_ast_estb_time.desc()) + + # 分页查询 + result = await PageUtil.paginate(db, query, page_num, page_size, is_page) + return result \ No newline at end of file diff --git a/vue-fastapi-backend/module_admin/dao/data_ast_content_dao.py b/vue-fastapi-backend/module_admin/dao/data_ast_content_dao.py index 1faaa23..9f6e97a 100644 --- a/vue-fastapi-backend/module_admin/dao/data_ast_content_dao.py +++ b/vue-fastapi-backend/module_admin/dao/data_ast_content_dao.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import aliased from sqlalchemy import delete, func, not_, select, update, or_, and_, desc from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.exc import IntegrityError -from module_admin.entity.do.data_ast_content_do import DataAstContent,DataAstContentRela,DataAstInfo,DataAstBookmarkRela,DataAstIndx +from module_admin.entity.do.data_ast_content_do import DataAstContent,DataAstContentRela,DataAssetInfo,DataAstBookmarkRela,DataAstIndx from module_admin.entity.do.user_do import SysUser from module_admin.entity.vo.data_ast_content_vo import DataCatalogPageQueryModel, DeleteDataCatalogModel,DataCatalogChild,DataAstBookmarkRelaRequest,DataAstIndxRequest,DataAstIndxResponse from utils.page_util import PageUtil @@ -151,7 +151,7 @@ class DataCatalogDAO: async def get_catalog_list(cls, db: AsyncSession, query_object: DataCatalogPageQueryModel, user_id: int, user_name: str, is_page: bool = False): # 创建别名对象 t1 = aliased(DataAstContentRela, name='t1') - t2 = aliased(DataAstInfo, name='t2') + t2 = aliased(DataAssetInfo, name='t2') t3 = aliased(DataAstBookmarkRela, name='t3') # 构建子查询1(对应subquery_t1) @@ -447,7 +447,7 @@ class DataCatalogDAO: :return: 去重后的数据资产树数据 """ # 创建别名对象 - a = aliased(DataAstInfo, name='a') + a = aliased(DataAssetInfo, name='a') b = aliased(DataAstContentRela, name='b') # 构建查询 @@ -714,7 +714,7 @@ class DataCatalogDAO: """ # 创建别名对象 t1 = aliased(DataAstContentRela, name='t1') - t2 = aliased(DataAstInfo, name='t2') + t2 = aliased(DataAssetInfo, name='t2') t3 = aliased(DataAstBookmarkRela, name='t3') @@ -756,10 +756,10 @@ class DataCatalogDAO: # 获取通过资产序号,获取资产编号 para_ast_no = ( select( - DataAstInfo.ast_no + DataAssetInfo.ast_no ) .where( - DataAstInfo.data_ast_no == catalog['data_ast_no'] + DataAssetInfo.data_ast_no == catalog['data_ast_no'] ) ) diff --git a/vue-fastapi-backend/module_admin/entity/do/data_ast_content_do.py b/vue-fastapi-backend/module_admin/entity/do/data_ast_content_do.py index d97ecda..4f1c929 100644 --- a/vue-fastapi-backend/module_admin/entity/do/data_ast_content_do.py +++ b/vue-fastapi-backend/module_admin/entity/do/data_ast_content_do.py @@ -1,6 +1,9 @@ from datetime import datetime from sqlalchemy import Column, DateTime, Integer, String, Text, DateTime, ForeignKey, Date, Double from config.database import Base +from sqlalchemy.sql import func + + # 定义数据资产目录表 class DataAstContent(Base): @@ -34,23 +37,44 @@ class DataAstContentRela(Base): -class DataAstInfo(Base): - __tablename__ = "t_data_ast_info" +# class DataAstInfo(Base): +# __tablename__ = "t_data_ast_info" - data_ast_no = Column(Integer, primary_key=True, autoincrement=True, comment='数据资产编号') - data_ast_eng_name = Column(String(255), nullable=False, comment='数据资产英文名称') - data_ast_cn_name = Column(String(255), nullable=True, comment='数据资产中文名称') - data_ast_type = Column(String(50), nullable=True, comment='数据资产类型') - data_ast_stat = Column(String(50), nullable=True, comment='数据资产状态') - data_ast_desc = Column(Text, nullable=True, comment='数据资产描述/说明') - data_ast_clas = Column(String(255), nullable=True, comment='数据资产标签') - data_ast_cont = Column(Text, nullable=True, comment='数据资产内容') - data_ast_faq = Column(Text, nullable=True, comment='数据资产常见问题') - data_ast_estb_time = Column(DateTime, default=datetime.now, comment='数据资产建立时间') - data_ast_upd_time = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='数据资产更新时间') - data_ast_src = Column(String(255), nullable=True, comment='数据资产来源') - ast_no = Column(Integer, nullable=True, comment='资产编号') +# data_ast_no = Column(Integer, primary_key=True, autoincrement=True, comment='数据资产编号') +# data_ast_eng_name = Column(String(255), nullable=False, comment='数据资产英文名称') +# data_ast_cn_name = Column(String(255), nullable=True, comment='数据资产中文名称') +# data_ast_type = Column(String(50), nullable=True, comment='数据资产类型') +# data_ast_stat = Column(String(50), nullable=True, comment='数据资产状态') +# data_ast_desc = Column(Text, nullable=True, comment='数据资产描述/说明') +# data_ast_clas = Column(String(255), nullable=True, comment='数据资产标签') +# data_ast_cont = Column(Text, nullable=True, comment='数据资产内容') +# data_ast_faq = Column(Text, nullable=True, comment='数据资产常见问题') +# data_ast_estb_time = Column(DateTime, default=datetime.now, comment='数据资产建立时间') +# data_ast_upd_time = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment='数据资产更新时间') +# data_ast_src = Column(String(255), nullable=True, comment='数据资产来源') +# ast_no = Column(Integer, nullable=True, comment='资产编号') +class DataAssetInfo(Base): + """ + 数据资产信息表 + """ + __tablename__ = 't_data_ast_info' + + data_ast_no = Column(Integer, primary_key=True, autoincrement=True, comment='资产序号') + data_ast_eng_name = Column(String(255), nullable=False, comment='表英文名称') + data_ast_cn_name = Column(String(255), nullable=True, comment='表中文名称') + data_ast_type = Column(String(50), nullable=True, comment='表类型(表、报表、数据应用等)') + data_ast_stat = Column(String(50), nullable=True, comment='表状态(有效/废弃)') + data_ast_desc = Column(Text, nullable=True, comment='资产描述/说明') + data_ast_screen = Column(String(255), nullable=True, comment='资产应用场景') + data_ast_scren_clas = Column(String(255), nullable=True, comment='应用场景分类') + data_ast_clas = Column(String(255), nullable=True, comment='资产标签') + data_ast_cont = Column(Text, nullable=True, comment='资产内容') + data_ast_faq = Column(Text, nullable=True, comment='资产常见问题') + data_ast_estb_time = Column(DateTime, nullable=True, server_default=func.now(), comment='资产建立时间') + data_ast_upd_time = Column(DateTime, nullable=True, server_default=func.now(), onupdate=func.now(), comment='资产更新时间') + data_ast_src = Column(String(255), nullable=True, comment='资产来源') + ast_no = Column(Integer, nullable=True, comment='资产编号') class DataAstBookmarkRela(Base): diff --git a/vue-fastapi-backend/module_admin/entity/vo/data_asset_vo.py b/vue-fastapi-backend/module_admin/entity/vo/data_asset_vo.py new file mode 100644 index 0000000..d59b37f --- /dev/null +++ b/vue-fastapi-backend/module_admin/entity/vo/data_asset_vo.py @@ -0,0 +1,96 @@ +from datetime import datetime +from pydantic import BaseModel, ConfigDict, Field +from pydantic.alias_generators import to_camel +from typing import List, Optional +from module_admin.annotation.pydantic_annotation import as_query + + +class DataAssetItemModel(BaseModel): + """ + 数据资产信息项模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True) + + + ast_no: int = Field(default=None, alias="astNo", description='数据资产编号(必填)') + data_ast_eng_name: str = Field(default=None, alias="dataAstEngName", description='数据资产英文名称(必填)') + data_ast_cn_name: str = Field(default=None, alias="dataAstCnName", description='数据资产中文名称') + data_ast_type: str = Field(default=None, alias="dataAstType", description='数据资产类型(表/报表/数据应用)(必填)') + data_ast_stat: str = Field(default=None, alias="dataAstStat", description='数据资产状态(有效/废弃)(必填)') + data_ast_desc: str = Field(default=None, alias="dataAstDesc", description='数据资产描述') + data_ast_screen: str = Field(default=None, alias="dataAstScreen", description='资产应用场景(API/智能问答等)') + data_ast_scren_clas: str = Field(default=None, alias="dataAstScrenClas", description='应用场景分类') + data_ast_clas: str = Field(default=None, alias="dataAstClas", description='数据资产标签') + data_ast_cont: str = Field(default=None, alias="dataAstCont", description='数据资产内容') + data_ast_faq: str = Field(default=None, alias="dataAstFaq", description='数据资产常见问题') + data_ast_src: str = Field(default=None, alias="dataAstSrc", description='数据资产来源(必填)') + version_no: str = Field(default="1", alias="versionNo", description='版本号(默认最新)') + ctrl_flag: str = Field(default=None, alias="ctrlFlag", description='操作类型 1:插入 2:删除 3:更新') + + +class DataAssetBatchModel(BaseModel): + """ + 数据资产批量操作模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True) + + data_assets: List[DataAssetItemModel] = Field(default=[], alias="dataAssets", description='数据资产列表') + + +class DataAssetResultModel(BaseModel): + """ + 数据资产操作结果模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True) + + success_count: int = Field(default=0, alias="successCount", description='成功数量') + failed_count: int = Field(default=0, alias="failedCount", description='失败数量') + success_items: List[str] = Field(default=[], alias="successItems", description='成功项') + failed_items: List[dict] = Field(default=[], alias="failedItems", description='失败项及原因') + + +class DataAssetQueryModel(BaseModel): + """ + 数据资产查询模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True) + + ast_no: Optional[str] = Field(default=None, alias="astNo", description='资产编号') + data_ast_eng_name: Optional[str] = Field(default=None, alias="dataAstEngName", description='资产英文名称') + data_ast_cn_name: Optional[str] = Field(default=None, alias="dataAstCnName", description='资产中文名称') + data_ast_type: Optional[str] = Field(default=None, alias="dataAstType", description='资产类型') + data_ast_stat: Optional[str] = Field(default=None, alias="dataAstStat", description='资产状态') + data_ast_src: Optional[str] = Field(default=None, alias="dataAstSrc", description='资产来源') + begin_time: Optional[str] = Field(default=None, alias="beginTime", description='开始时间') + end_time: Optional[str] = Field(default=None, alias="endTime", description='结束时间') + + +@as_query +class DataAssetPageQueryModel(DataAssetQueryModel): + """ + 数据资产分页查询模型 + """ + + page_num: int = Field(default=1, alias="pageNum", description='当前页码') + page_size: int = Field(default=10, alias="pageSize", description='每页记录数') + + +@as_query +class DataAssetSearchModel(BaseModel): + """ + 数据资产综合查询模型 + """ + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, from_attributes=True) + + name: Optional[str] = Field(default=None, alias="name", description='资产名称(英文名或中文名)') + data_ast_type: Optional[str] = Field(default=None, alias="dataAstType", description='资产类型') + data_ast_screen: Optional[str] = Field(default=None, alias="dataAstScreen", description='资产应用场景') + data_ast_clas: Optional[str] = Field(default=None, alias="dataAstClas", description='资产标签') + data_ast_src: Optional[str] = Field(default=None, alias="dataAstSrc", description='资产来源') + page_num: int = Field(default=1, alias="pageNum", description='当前页码') + page_size: int = Field(default=10, alias="pageSize", description='每页记录数') \ No newline at end of file diff --git a/vue-fastapi-backend/module_admin/service/data_asset_service.py b/vue-fastapi-backend/module_admin/service/data_asset_service.py new file mode 100644 index 0000000..f367866 --- /dev/null +++ b/vue-fastapi-backend/module_admin/service/data_asset_service.py @@ -0,0 +1,176 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from module_admin.dao.data_asset_dao import DataAssetDao +from module_admin.entity.vo.data_asset_vo import DataAssetItemModel, DataAssetBatchModel, DataAssetResultModel, DataAssetPageQueryModel +from exceptions.exception import ServiceException +from typing import Dict, Any, List +from utils.common_util import export_list2excel + + +class DataAssetService: + """ + 数据资产信息模块服务层 + """ + + @classmethod + async def get_data_asset_list_services( + cls, query_db: AsyncSession, query_object: DataAssetPageQueryModel, is_page: bool = False + ): + """ + 获取数据资产信息列表service + + :param query_db: orm对象 + :param query_object: 查询参数对象 + :param is_page: 是否开启分页 + :return: 数据资产信息列表对象 + """ + data_asset_list_result = await DataAssetDao.get_data_asset_list(query_db, query_object, is_page) + return data_asset_list_result + + @classmethod + async def batch_process_data_asset_services( + cls, query_db: AsyncSession, batch_object: DataAssetBatchModel + ): + """ + 批量处理数据资产信息service + + :param query_db: orm对象 + :param batch_object: 批量处理参数对象 + :return: 处理结果对象 + """ + result = DataAssetResultModel() + + for item in batch_object.data_assets: + try: + # 转换对象为字典,并移除ctrl_flag,version_no字段 + data_dict = item.model_dump() + ctrl_flag = data_dict.pop("ctrl_flag") + version_no = data_dict.pop("version_no") + + # 检查数据资产是否存在 + existing_asset = await DataAssetDao.get_data_asset_by_ast_no(query_db, item.ast_no) + + # 根据ctrl_flag进行不同的操作 + if ctrl_flag == "1": # 插入 + if existing_asset: + raise ServiceException(f"数据资产编号 {item.ast_no} 已存在,无法插入") + await DataAssetDao.insert_data_asset(query_db, data_dict) + result.success_count += 1 + result.success_items.append(item.ast_no) + elif ctrl_flag == "2": # 删除 + if not existing_asset: + raise ServiceException(f"数据资产编号 {item.ast_no} 不存在,无法删除") + await DataAssetDao.delete_data_asset(query_db, item.ast_no) + result.success_count += 1 + result.success_items.append(item.ast_no) + elif ctrl_flag == "3": # 更新 + if not existing_asset: + raise ServiceException(f"数据资产编号 {item.ast_no} 不存在,无法更新") + await DataAssetDao.update_data_asset(query_db, item.ast_no, data_dict) + result.success_count += 1 + result.success_items.append(item.ast_no) + else: + raise ServiceException(f"未知的操作类型: {ctrl_flag}") + + except Exception as e: + result.failed_count += 1 + result.failed_items.append({"ast_no": item.ast_no, "error": str(e)}) + + # 提交事务 + await query_db.commit() + + return result + + @classmethod + async def get_data_asset_sources_services(cls, query_db: AsyncSession): + """ + 获取所有数据资产来源service + + :param query_db: orm对象 + :return: 数据资产来源列表 + """ + sources = await DataAssetDao.get_data_asset_sources(query_db) + + # 构造父节点 + formatted_data = [ + { + "name": "表数据资产", + "children": [ + { + "id": str(index), # 转换为字符串类型id + "name": source # 原始来源名称 + } + for index, source in enumerate(sources, 1) # 从1开始编号 + ] + }, + { + "name": "接口数据资产", + "children": [] + }, + { + "name": "数据治理资产", + "children": [] + } + ] + + return formatted_data + + @classmethod + async def search_data_assets_services( + cls, query_db: AsyncSession, search_params: dict, page_num: int = 1, page_size: int = 10, is_page: bool = True + ): + """ + 综合查询数据资产信息service + + :param query_db: orm对象 + :param search_params: 查询参数字典 + :param page_num: 页码 + :param page_size: 每页大小 + :param is_page: 是否分页 + :return: 查询结果 + """ + search_result = await DataAssetDao.search_data_assets( + query_db, search_params, is_page, page_num, page_size + ) + return search_result + + + + @staticmethod + async def export_data_asset_list_services(data_assets_list: list): + mapping_dict = { + "astNo": "资产编号", + "dataAstEngName": "表英文名称", + "dataAstCnName": "表中文名称", + "dataAstType": "表类型", + "dataAstStat": "表状态", + "dataAstDesc": "表描述", + "dataAstScreen": "资产应用场景", + "dataAstScrenClas": "应用场景分类", + "dataAstClas": "资产标签", + "dataAstCont": "资产内容", + "dataAstFaq": "资产常见问题", + "dataAstSrc": "资产来源", + "dataAstEstbTime": "资产建立时间", + "dataAstUpdTime": "资产更新时间" + } + + # 1. 获取映射关系的键顺序(确保列顺序一致) + keys_order = list(mapping_dict.keys()) + + new_data = [] + for item in data_assets_list: + item_dict = item.__dict__ if not isinstance(item, dict) else item + cleaned_item = {k: (v if v is not None else "") for k, v in item_dict.items()} + + # 处理状态字段 + cleaned_item["dataAstStat"] = "有效" if cleaned_item.get("dataAstStat") == "有效" else "废弃" + + # 按mapping_dict的键顺序构建数据 + mapped_item = { + mapping_dict[key]: cleaned_item.get(key, "") + for key in keys_order + } + new_data.append(mapped_item) + + # 直接调用export_list2excel,只传入数据参数 + return export_list2excel(new_data) \ No newline at end of file diff --git a/vue-fastapi-backend/module_admin/service/data_ast_content_service.py b/vue-fastapi-backend/module_admin/service/data_ast_content_service.py index 8b0cff6..43b109b 100644 --- a/vue-fastapi-backend/module_admin/service/data_ast_content_service.py +++ b/vue-fastapi-backend/module_admin/service/data_ast_content_service.py @@ -669,7 +669,7 @@ class DataCatalogService: indx_list_dict = await DataCatalogDAO.get_data_ast_indx_list(query_db, query_object) indx_names = [item["indx_name"] for item in indx_list_dict] indx_vals = [item["indx_val"] for item in indx_list_dict] - CurrentConfig.ONLINE_HOST = "/assets/js/echarts.min.js" + CurrentConfig.ONLINE_HOST = "/assets/js/" # 创建独立图表 pie = ( diff --git a/vue-fastapi-backend/server.py b/vue-fastapi-backend/server.py index e7cca0f..545e875 100644 --- a/vue-fastapi-backend/server.py +++ b/vue-fastapi-backend/server.py @@ -34,6 +34,7 @@ from module_admin.controller.fccbd_controller import fccbdController from module_admin.controller.cdplb_controller import cdplbController from module_admin.controller.sscf_controller import sscfController from module_admin.controller.vecset_controller import vecsetController +from module_admin.controller.data_asset_controller import dataAssetController from sub_applications.handle import handle_sub_applications from utils.common_util import worship from utils.log_util import logger @@ -101,6 +102,7 @@ controller_list = [ {'router': cdplbController, 'tags': ['智能数据-词典批量补充']}, {'router': sscfController, 'tags': ['智能数据-短句配置']}, {'router': vecsetController, 'tags': ['智能数据-全句配置']}, + {'router': dataAssetController, 'tags': ['系统管理-数据资产详情']}, ] for controller in controller_list: diff --git a/vue-fastapi-frontend/package.json b/vue-fastapi-frontend/package.json index 0987104..2b9d916 100644 --- a/vue-fastapi-frontend/package.json +++ b/vue-fastapi-frontend/package.json @@ -29,6 +29,7 @@ "@vueuse/core": "10.11.0", "ant-design-vue": "^4.1.1", "axios": "0.28.1", + "codemirror": "^5.65.19", "codemirror-editor-vue3": "^2.8.0", "echarts": "5.5.1", "element-plus": "2.8.0", diff --git a/vue-fastapi-frontend/src/api/aichat/aichat.js b/vue-fastapi-frontend/src/api/aichat/aichat.js index c0b15e2..a0b353d 100644 --- a/vue-fastapi-frontend/src/api/aichat/aichat.js +++ b/vue-fastapi-frontend/src/api/aichat/aichat.js @@ -48,3 +48,10 @@ export async function addChat(data) { data: data }) } +export async function postDataQuery(data) { + return request({ + url: '/dataquery-api/chat/nl2sql_client_chat', + method: 'post', + data: data + }) +} diff --git a/vue-fastapi-frontend/src/api/dataAsset/assetDetail.js b/vue-fastapi-frontend/src/api/dataAsset/assetDetail.js new file mode 100644 index 0000000..5aee9e3 --- /dev/null +++ b/vue-fastapi-frontend/src/api/dataAsset/assetDetail.js @@ -0,0 +1,25 @@ +import request from '@/utils/request' + +export function getSearch(params) { + return request({ + url: '/default-api/system/dataAsset/search', + method: 'get', + params, + }) +} + +export function batch(data) { + return request({ + url: '/default-api/system/dataAsset/batch', + method: 'post', + data, + }) +} + +export function deptTreeSelect() { + return request({ + url: '/default-api/system/dataAsset/sources', + method: 'get' + }) + } + \ No newline at end of file diff --git a/vue-fastapi-frontend/src/components/codemirror/SQLCodeMirror.vue b/vue-fastapi-frontend/src/components/codemirror/SQLCodeMirror.vue index eb817ca..638bef5 100644 --- a/vue-fastapi-frontend/src/components/codemirror/SQLCodeMirror.vue +++ b/vue-fastapi-frontend/src/components/codemirror/SQLCodeMirror.vue @@ -59,11 +59,15 @@ const sqlOptions = { lint: true, // 打开json校验 } const value = ref("") -watch(() => props.data, +watch(() => value.value, (val) =>{ value.value = sqlFormatter.format(val) } ) +onMounted(()=>{ + value.value = props.data +} +) diff --git a/vue-fastapi-frontend/src/utils/request.js b/vue-fastapi-frontend/src/utils/request.js index e13c8f9..1270ae7 100644 --- a/vue-fastapi-frontend/src/utils/request.js +++ b/vue-fastapi-frontend/src/utils/request.js @@ -17,7 +17,7 @@ const service = axios.create({ // axios中请求配置有baseURL选项,表示请求URL公共部分 // baseURL: import.meta.env.VITE_APP_BASE_API, // 超时 - timeout: 10000 + timeout: 100000 }) // request拦截器 diff --git a/vue-fastapi-frontend/src/views/aichat/MdRenderer.vue b/vue-fastapi-frontend/src/views/aichat/MdRenderer.vue index c7303db..b93546d 100644 --- a/vue-fastapi-frontend/src/views/aichat/MdRenderer.vue +++ b/vue-fastapi-frontend/src/views/aichat/MdRenderer.vue @@ -22,6 +22,7 @@ diff --git a/vue-fastapi-frontend/src/views/dataint/dataquery/dataquerychat.vue b/vue-fastapi-frontend/src/views/dataint/dataquery/dataquerychat.vue index 397ee70..925ce83 100644 --- a/vue-fastapi-frontend/src/views/dataint/dataquery/dataquerychat.vue +++ b/vue-fastapi-frontend/src/views/dataint/dataquery/dataquerychat.vue @@ -15,18 +15,26 @@ @@ -61,22 +154,16 @@
- - - + + +
@@ -85,24 +172,16 @@ diff --git a/vue-fastapi-frontend/src/views/dataint/dataquery/index.vue b/vue-fastapi-frontend/src/views/dataint/dataquery/index.vue index 4a534d5..f2f1d0f 100644 --- a/vue-fastapi-frontend/src/views/dataint/dataquery/index.vue +++ b/vue-fastapi-frontend/src/views/dataint/dataquery/index.vue @@ -16,14 +16,181 @@
+ + + +
+ +
+
+ + + +
+
+ + 条件设置 + + + + + + + + + + + + + + + +
+ +
+ + + + +
+
+
+
+
@@ -32,28 +199,20 @@ import { ref, onMounted, reactive, nextTick, computed } from 'vue' import { v4 as uuidv4 } from 'uuid'; import Cookies from 'js-cookie' import { useRoute } from 'vue-router' -import { listChatHistory, getChatList, DeleteChatSession } from "@/api/aichat/aichat"; import Dataquerychat from "./dataquerychat.vue"; const route = useRoute() const { proxy } = getCurrentInstance(); -const { - params: { accessToken } -} = route -const props = defineProps({ - is_large: Boolean, - chatDataList: Array, -}) -const AiChatRef = ref() +const chatDataList = ref([]) +const dataQueryRef = ref() const chatLogeData = ref([]) const show = ref(false) +const showDrawer = ref(false) const currentRecordList = ref([]) +const chooseData = ref("") +const treeData = ref([]) +const tableData = ref([]) +const inputValue = ref("") const sessionId = ref(Cookies.get("chatSessionId")) // 当前历史记录Id 默认为'new' -const mouseId = ref('') - - - - - function handleScroll(event) { if (event.scrollTop === 0 && currentRecordList.value.length>0) { @@ -61,52 +220,185 @@ function handleScroll(event) { event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height) } } - +function sendChatHandle(){ + let queryObj = { + "type": "question", + "content": inputValue.value.trim(), + "time": formatDate(new Date()), + } + let inputVal = inputValue.value + dataQueryRef.value.handleSend(queryObj,inputVal); + showDrawer.value = false +} function clickListHandle(item){ - getChatList(item.sessionId).then(res=>{ - currentRecordList.value = [] - let array = res.data - for (let i = 0; i < array.length; i++) { - if (array[i].type === 'answer'){ - array[i].content = JSON.parse(array[i].content) - } - if (array[i].type === 'question'){ - array[i].file = JSON.parse(array[i].file) + +} +function padZero(num) { + return num < 10 ? '0' + num : num; +} +function formatDate(date) { + const year = date.getFullYear(); + const month = padZero(date.getMonth() + 1); // 月份从0开始,所以需要+1 + const day = padZero(date.getDate()); + const hours = padZero(date.getHours()); + const minutes = padZero(date.getMinutes()); + const seconds = padZero(date.getSeconds()); + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; +} +function remove(index){ + uncheckTree(tableData.value[index].name) + tableData.value.splice(index,1) +} +function uncheckTree(name){ + if (treeData.value.length>0){ + for (let i = 0; i < treeData.value.length; i++) { + if (treeData.value[i].children && treeData.value[i].children.length>0){ + for (let j = 0; j < treeData.value[i].children.length; j++) { + if (treeData.value[i].children[j].value === name){ + treeData.value[i].children[j].checked = false + } + } } - currentRecordList.value.push(array[i]) - AiChatRef.value.setScrollBottom() } - show.value = false - sessionId.value = item.sessionId - Cookies.set("chatSessionId",sessionId.value) - }) -} -watch(() => props.chatDataList, value => currentRecordList.value = JSON.parse(JSON.stringify(value))) -onMounted( - ()=>{ - if (Cookies.get("chatSessionId")){ - //调用子页面的赋值方法,给子页面赋值 - getChatList(Cookies.get("chatSessionId")).then(res=>{ - let array = res.data - if (array && array.length >0){ - for (let i = 0; i < array.length; i++) { - if (array[i].type === 'answer'){ - array[i].content = JSON.parse(array[i].content) - } - if (array[i].type === 'question'){ - array[i].file = JSON.parse(array[i].file) - } + } +} +function changeTableIndex(type,index){ + if(type === 'top'){ + let temp = tableData.value[index] + tableData.value[index] = tableData.value[index - 1] + tableData.value[index - 1] = temp + } + if (type === 'bottom'){ + let temp = tableData.value[index] + tableData.value[index] = tableData.value[index + 1] + tableData.value[index + 1] = temp + } +} +function showEdit(data){ + let array = JSON.parse(JSON.stringify(data.content.datas)) + let compareArr = JSON.parse(JSON.stringify(data.content.query)) + compareArr.push(...JSON.parse(JSON.stringify(data.content.result))) + treeData.value = [] + if (array.length >0){ + for (let i = 0; i < array.length; i++) { + let tab_cn_name = array[i].tab_cn_name; + let fld_cn_name = array[i].fld_cn_name; + let fld_type = array[i].fld_type; + if (treeData.value.length>0){ + if (treeData.value.some(item=>item.label === tab_cn_name)){ + for (let j = 0; j < treeData.value.length; j++) { + if (treeData.value[j].label === tab_cn_name){ + treeData.value[j].children.push({ + label:fld_cn_name +" | "+ fld_type, + value: fld_cn_name, + checked: compareArr.length>0?compareArr.some(item=>item.name === fld_cn_name):false, + showStar: true + }) } } - currentRecordList.value = array - }).then(()=>{ - AiChatRef.value.setScrollBottom() - }) + }else { + treeData.value.push({ + label: tab_cn_name, + value: tab_cn_name, + checked: false, + showStar: false, + children:[{ + label:fld_cn_name +" | "+ fld_type, + value: fld_cn_name, + checked: false, + showStar: true + }] + }) + } }else { - Cookies.set("chatSessionId",uuidv4()) + treeData.value.push({ + label: tab_cn_name, + value: tab_cn_name, + checked: false, + showStar: false, + children:[{ + label:fld_cn_name +" | "+ fld_type, + value: fld_cn_name, + checked: false, + showStar: true, + }] + }) + } + } + } + tableData.value = [] + tableData.value.push(...data.content.query) + tableData.value.push(...data.content.result) + showDrawer.value = true +} +watch(chooseData, val => { + proxy.$refs["dataQueryTreeRef"].filter(val); +}); +function download(data){ + +} +function handleNodeClick(data){ + if (tableData.value.length>0){ + if (tableData.value.some(item=>item.name===data.value)){ + let index = -1; + for (let i = 0; i < tableData.value.length; i++) { + if (tableData.value[i].name === data.value){ + index = i; + } + } + if (index >=0){ + tableData.value.splice(index, 1); } + }else { + tableData.value.push({ + name:data.value, + cd_type:'', + ct_type:'', + default_value:'' + }) } -) + } + data.checked = !data.checked +} +const filterNode = (value, data) => { + if (!value) return true; + return data.label.indexOf(value) !== -1; +}; +function formatDefaultValue(item){ + let value = item.default_value + if (item.cd_type === '输出结果'){ + return item.name + } + if (item.ct_type === 'dateRangePicker'){ + if (item.dateRangeValue){ + return "开始日期为"+item.dateRangeValue[0]+"截止日期为"+item.dateRangeValue[1] + }else { + return "" + } + } + return value +} +watch(tableData,(newValue,oldValueValue) =>{ + if (tableData.value.length>0){ + let resultList = [] + let queryList = [] + for (let i = 0; i < tableData.value.length; i++) { + if (tableData.value[i].cd_type === '输出结果'){ + resultList.push(formatDefaultValue(tableData.value[i])) + }else{ + queryList.push(tableData.value[i].name+"为"+formatDefaultValue(tableData.value[i])) + } + } + if (resultList.length > 0){ + queryList.push("输出结果为"+resultList.join("和")) + } + let result = "查询"+queryList.join(",") + inputValue.value = result + } +},{ deep: true}) + + diff --git a/vue-fastapi-frontend/src/views/meta/metaInfo/index.vue b/vue-fastapi-frontend/src/views/meta/metaInfo/index.vue index 15d51a8..c32b63a 100644 --- a/vue-fastapi-frontend/src/views/meta/metaInfo/index.vue +++ b/vue-fastapi-frontend/src/views/meta/metaInfo/index.vue @@ -311,7 +311,7 @@ 业务关系 血缘关系 - + @@ -545,7 +545,6 @@ import SQLCodeMirror from "@/components/codemirror/SQLCodeMirror.vue"; import cache from "@/plugins/cache"; - const data = reactive({ queryParams:{ ssysCd:'', @@ -608,6 +607,7 @@ const activeColumnTab = ref("column"); const { proxy } = getCurrentInstance(); const changedColumns = ref([]) + function changeColumnTab(){ if (activeColumnTab.value === 'proc'){ procStr.value = "--基金量化产品监管报送存储过程:ADS.SP_ADS_SAC_QNTPRD_ALL\n" + @@ -1058,6 +1058,7 @@ getDatabaseList() handleQuery() }) +