Browse Source

Merge remote-tracking branch 'origin/master'

master
xueyinfei 3 weeks ago
parent
commit
e27a79fdbe
  1. 24
      vue-fastapi-backend/module_admin/controller/data_asset_controller.py
  2. 6
      vue-fastapi-backend/module_admin/dao/data_asset_dao.py
  3. 5
      vue-fastapi-backend/module_admin/dao/dataast_dao.py
  4. 2
      vue-fastapi-backend/module_admin/service/approval_service.py
  5. 22
      vue-fastapi-backend/module_admin/service/dataast_service.py
  6. 2
      vue-fastapi-backend/module_admin/service/meta_service.py
  7. 32
      vue-fastapi-backend/module_admin/service/metasecurity_service.py
  8. 1
      vue-fastapi-backend/requirements.txt
  9. 8
      vue-fastapi-frontend/src/api/dataAsset/assetDetail.js
  10. 13
      vue-fastapi-frontend/src/api/meta/metaInfo.js
  11. 4
      vue-fastapi-frontend/src/views/dataAsset/assetDetail/index.vue
  12. 202
      vue-fastapi-frontend/src/views/datastd/main/components/AddEditForm.vue
  13. 32
      vue-fastapi-frontend/src/views/meta/metatask/index.vue

24
vue-fastapi-backend/module_admin/controller/data_asset_controller.py

@ -212,6 +212,30 @@ async def add_dataast_appr(
return ResponseUtil.success(msg=add_dataast_result.message) return ResponseUtil.success(msg=add_dataast_result.message)
@dataAssetController.post('/delastappr')
@Log(title='数据资产删除申请', business_type=BusinessType.DELETE)
async def delete_dataast_appr(
request: Request,
delete_dataasts: DataAstApprBatchModel,
query_db: AsyncSession = Depends(get_db),
current_user: CurrentUserModel = Depends(LoginService.get_current_user),
):
now = datetime.now()
for delete_dataast in delete_dataasts.assetItems:
delete_dataast.upd_prsn = current_user.user.user_name
delete_dataast.upd_time = now
if not delete_dataast.create_by:
delete_dataast.create_by = current_user.user.user_name
if not delete_dataast.create_time:
delete_dataast.create_time = now
delete_dataast_result = await DataAstService.delete_dataasts_appr(
query_db, delete_dataasts, current_user.user.user_name
)
logger.info(delete_dataast_result.message)
return ResponseUtil.success(msg=delete_dataast_result.message)
@dataAssetController.get('/listastappr', response_model=PageResponseModel @dataAssetController.get('/listastappr', response_model=PageResponseModel
) )
async def get_ast_main_appr_list( async def get_ast_main_appr_list(

6
vue-fastapi-backend/module_admin/dao/data_asset_dao.py

@ -84,7 +84,11 @@ class DataAssetDao:
:return: 数据资产信息对象 :return: 数据资产信息对象
""" """
data_asset = ( data_asset = (
await db.execute(select(DataAssetInfoAppr).where(DataAssetInfoAppr.ast_no == ast_no)) await db.execute(
select(DataAssetInfoAppr)
.where(DataAssetInfoAppr.ast_no == ast_no)
.order_by(DataAssetInfoAppr.create_time.desc())
)
).scalars().first() ).scalars().first()
return data_asset return data_asset

5
vue-fastapi-backend/module_admin/dao/dataast_dao.py

@ -125,6 +125,11 @@ class DataAstDao:
await db.rollback() await db.rollback()
raise e raise e
@classmethod
async def delete_dataast_data_by_ast_no(cls, db: AsyncSession, ast_no: int):
await db.execute(delete(DataAssetInfo).where(DataAssetInfo.ast_no == ast_no))
await db.flush()
@classmethod @classmethod
async def update_data_ast_appr(cls, db: AsyncSession, update_data: DataAstInfoModel): async def update_data_ast_appr(cls, db: AsyncSession, update_data: DataAstInfoModel):

2
vue-fastapi-backend/module_admin/service/approval_service.py

@ -253,6 +253,8 @@ class ApprovalService:
# logger.info(f"发现新增类型变更,准备添加数据资产,AST_NO: {appr_model.ast_no}") # logger.info(f"发现新增类型变更,准备添加数据资产,AST_NO: {appr_model.ast_no}")
main_model = DataAstInfoModel(**appr_model.model_dump(exclude_unset=True, by_alias=True)) main_model = DataAstInfoModel(**appr_model.model_dump(exclude_unset=True, by_alias=True))
await DataAstDao.add_dataast_data(result_db, main_model) await DataAstDao.add_dataast_data(result_db, main_model)
elif change_type == "delete" and appr_model.ast_no is not None:
await DataAstDao.delete_dataast_data_by_ast_no(result_db, appr_model.ast_no)
# logger.info(f"数据资产添加成功,AST_NO: {main_model.ast_no}") # logger.info(f"数据资产添加成功,AST_NO: {main_model.ast_no}")
# 更新 approStatus 状态 # 更新 approStatus 状态

22
vue-fastapi-backend/module_admin/service/dataast_service.py

@ -65,6 +65,28 @@ class DataAstService:
return CrudResponseModel(is_success=True, message='新增数据资产成功') return CrudResponseModel(is_success=True, message='新增数据资产成功')
@classmethod
async def delete_dataasts_appr(cls, query_db: AsyncSession, models: DataAstApprBatchModel, username: str):
flowId = str(uuid.uuid4())
for model in models.assetItems:
original_onum = model.onum
appr_onum = str(uuid.uuid4())
model.onum = appr_onum
apprModel = DataAstApprModel(**model.model_dump(exclude_unset=True, by_alias=True))
apprModel.changeType = "delete"
apprModel.compareId = original_onum
apprModel.oldInstId = str(model.ast_no) if model.ast_no is not None else original_onum
apprModel.approStatus = "waiting"
apprModel.flowId = flowId
await DataAstDao.add_dataast_appr(query_db, apprModel)
applyModel = ApplyModel()
applyModel.businessType = "dataAssetMain"
applyModel.businessId = flowId
applyModel.applicant = username
await ApprovalService.apply_services(query_db, applyModel, 'dataAssetMain')
return CrudResponseModel(is_success=True, message='删除数据资产申请提交成功')
@classmethod @classmethod
async def get_ast_main_appr_list(cls, query_db: AsyncSession, flowId: str, is_page: bool = False): async def get_ast_main_appr_list(cls, query_db: AsyncSession, flowId: str, is_page: bool = False):
return await DataAstDao.get_ast_main_appr_list(flowId, query_db) return await DataAstDao.get_ast_main_appr_list(flowId, query_db)

2
vue-fastapi-backend/module_admin/service/meta_service.py

@ -56,7 +56,7 @@ class MetaService:
table['hasAsset'] = '1' # 已发布 table['hasAsset'] = '1' # 已发布
else: else:
hasAsset2 = await DataAssetDao.get_data_asset_appr_by_ast_no(result_db, table['extractOnum']) hasAsset2 = await DataAssetDao.get_data_asset_appr_by_ast_no(result_db, table['extractOnum'])
if hasAsset2: if hasAsset2 and hasAsset2.approStatus == 'waiting':
table['hasAsset'] = '2' # 发布中 table['hasAsset'] = '2' # 发布中
table['batchTabClas'] = CamelCaseUtil.transform_result(tab_list) table['batchTabClas'] = CamelCaseUtil.transform_result(tab_list)
return meta_rel_list return meta_rel_list

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

@ -23,6 +23,7 @@ from sqlglot.expressions import Table
from sqlglot import exp ,parse_one from sqlglot import exp ,parse_one
from typing import Set from typing import Set
from sqlparse.tokens import Keyword, DML from sqlparse.tokens import Keyword, DML
from urllib.parse import quote_plus
class MetaSecurityService: class MetaSecurityService:
""" """
数据源安全管理模块服务层 数据源安全管理模块服务层
@ -379,7 +380,9 @@ class MetaSecurityService:
# 1️⃣ 去掉 jdbc 前缀 # 1️⃣ 去掉 jdbc 前缀
jdbc_prefixes = { jdbc_prefixes = {
"jdbc:mysql://": len("jdbc:mysql://"), "jdbc:mysql://": len("jdbc:mysql://"),
"jdbc:postgresql://": len("jdbc:postgresql://") "jdbc:postgresql://": len("jdbc:postgresql://"),
"jdbc:oracle:thin:@//": len("jdbc:oracle:thin:@//"),
"jdbc:oracle:thin:@": len("jdbc:oracle:thin:@")
} }
for prefix, length in jdbc_prefixes.items(): for prefix, length in jdbc_prefixes.items():
if address.startswith(prefix): if address.startswith(prefix):
@ -410,6 +413,21 @@ class MetaSecurityService:
pool_pre_ping=True, pool_pre_ping=True,
connect_args={"timeout": 5} # ⭐ 关键 connect_args={"timeout": 5} # ⭐ 关键
) )
elif db_type.lower() == "oracle":
address = db_params["address"].lstrip("/")
user = quote_plus(db_params["user"])
password = quote_plus(db_params["password"])
connect_type = (db_params.get("connectType") or "").upper()
service_or_sid = quote_plus(db_params.get("database", ""))
if connect_type == "ORACLE_SID":
conn_str = f"oracle+oracledb://{user}:{password}@{address}/?sid={service_or_sid}"
else:
conn_str = f"oracle+oracledb://{user}:{password}@{address}/?service_name={service_or_sid}"
engine = create_async_engine(
conn_str,
pool_pre_ping=True,
connect_args={"transport_connect_timeout": 5}
)
else: else:
raise ValueError("不支持的数据库类型") raise ValueError("不支持的数据库类型")
@ -438,6 +456,7 @@ class MetaSecurityService:
try: try:
async with async_session() as session: async with async_session() as session:
if (db_type or "").upper() == "POSTGRESQL":
await session.execute(text("SET statement_timeout = 30000")) await session.execute(text("SET statement_timeout = 30000"))
# ⭐ 原始数量 # ⭐ 原始数量
if sql_type == "原始结果": if sql_type == "原始结果":
@ -474,6 +493,7 @@ class MetaSecurityService:
if select: if select:
select.set("order", None) select.set("order", None)
select.set("limit", None) select.set("limit", None)
select.set("offset", None)
cleaned_sql = parsed.sql(dialect=dialect) cleaned_sql = parsed.sql(dialect=dialect)
return f"SELECT COUNT(*) AS cnt FROM ({cleaned_sql}) t" return f"SELECT COUNT(*) AS cnt FROM ({cleaned_sql}) t"
@ -495,10 +515,18 @@ class MetaSecurityService:
elif db_type.lower() == "mysql": elif db_type.lower() == "mysql":
# MySQL: 直接查询表字段(MySQL 没有 schema 的概念) # MySQL: 直接查询表字段(MySQL 没有 schema 的概念)
query = f""" query = f"""
SELECT COLUMN_NAME SELECT COLUMN_NAME AS column_name
FROM INFORMATION_SCHEMA.COLUMNS FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '{table}' WHERE TABLE_NAME = '{table}'
""" """
elif db_type.lower() == "oracle":
query = f"""
SELECT COLUMN_NAME AS "column_name"
FROM ALL_TAB_COLUMNS
WHERE OWNER = UPPER('{schema}')
AND TABLE_NAME = UPPER('{table}')
ORDER BY COLUMN_ID
"""
else: else:
raise ValueError(f"暂不支持数据库类型: {db_type}") raise ValueError(f"暂不支持数据库类型: {db_type}")

1
vue-fastapi-backend/requirements.txt

@ -34,6 +34,7 @@ mdurl==0.1.2
minio==7.2.14 minio==7.2.14
numpy==2.2.1 numpy==2.2.1
openpyxl==3.1.5 openpyxl==3.1.5
oracledb==2.2.1
orjson==3.10.12 orjson==3.10.12
packaging==25.0 packaging==25.0
pandas==2.2.2 pandas==2.2.2

8
vue-fastapi-frontend/src/api/dataAsset/assetDetail.js

@ -24,6 +24,14 @@ export function batch(data) {
}) })
} }
export function delAstAppr(data) {
return request({
url: '/default-api/system/dataAsset/delastappr',
method: 'post',
data,
})
}
export function deptTreeSelect() { export function deptTreeSelect() {
return request({ return request({
url: '/default-api/system/dataAsset/sources', url: '/default-api/system/dataAsset/sources',

13
vue-fastapi-frontend/src/api/meta/metaInfo.js

@ -1,4 +1,5 @@
import request from '@/utils/request' import request from '@/utils/request'
import cache from '@/plugins/cache'
// 查询参数列表 // 查询参数列表
export function getDataSourceList(query) { export function getDataSourceList(query) {
@ -100,7 +101,17 @@ export function getProcData(param){
params: param params: param
}) })
} }
export function getschemaById(id) {
const query = {
userName: cache.local.get("username"),
password: cache.local.get("password")
}
return request({
url: '/ds-api/dolphinscheduler/datasources/schemas?datasource='+id,
method: 'get',
headers: {dashUserName:query.userName,dashPassword:query.password}
})
}

4
vue-fastapi-frontend/src/views/dataAsset/assetDetail/index.vue

@ -256,7 +256,7 @@
</template> </template>
<script setup name="AssetDetail"> <script setup name="AssetDetail">
import { getSearch, batch, deptTreeSelect } from "@/api/dataAsset/assetDetail"; import { getSearch, batch, delAstAppr, deptTreeSelect } from "@/api/dataAsset/assetDetail";
import {getMetaClasList} from "@/api/meta/metaInfo" import {getMetaClasList} from "@/api/meta/metaInfo"
import cache from "@/plugins/cache"; import cache from "@/plugins/cache";
@ -520,7 +520,7 @@ const handleDelete = (row) => {
) )
}) })
proxy.$modal.confirm('是否确认删除?').then(function () { proxy.$modal.confirm('是否确认删除?').then(function () {
return batch({ dataAssets: submitasset }); return delAstAppr({ assetItems: submitasset });
}).then(() => { }).then(() => {
getList(); getList();
proxy.$modal.msgSuccess("删除成功"); proxy.$modal.msgSuccess("删除成功");

202
vue-fastapi-frontend/src/views/datastd/main/components/AddEditForm.vue

@ -1,5 +1,6 @@
<template> <template>
<el-dialog <el-dialog
class="std-form-dialog"
width="900px" width="900px"
append-to-body append-to-body
:title="isShow ? '查看数据标准' : isEdit ? '修改数据标准' : '新增数据标准'" :title="isShow ? '查看数据标准' : isEdit ? '修改数据标准' : '新增数据标准'"
@ -7,6 +8,7 @@
@close="handleClose" @close="handleClose"
> >
<el-form <el-form
class="std-form"
:model="formData" :model="formData"
ref="formRef" ref="formRef"
label-width="120px" label-width="120px"
@ -168,17 +170,125 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="安全等级" prop="dataSecLvl">
<el-select
v-model="formData.dataSecLvl"
:disabled="isShow"
placeholder="请选择安全等级"
>
<el-option label="1级" value="1" />
<el-option label="2级" value="2" />
<el-option label="3级" value="3" />
<el-option label="4级" value="4" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="代码编号" prop="cdId">
<el-input
v-model="formData.cdNo"
placeholder="代码编号"
clearable
:disabled="true"
>
<template #append>
<el-button
icon="Edit"
:disabled="isShow"
@click="codeVisible = true"
/>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="业务认责部门" prop="dataStdBusiOwnershipDept">
<el-tree-select
v-model="formData.dataStdBusiOwnershipDept"
:data="deptOptions"
:props="{ value: 'label', label: 'label', children: 'children' }"
value-key="id"
:disabled="isShow"
placeholder="请选择业务认责部门"
check-strictly
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="业务认责人员" prop="dataStdBusiOwnershipPrsn">
<el-select
v-model="formData.dataStdBusiOwnershipPrsn"
:disabled="isShow"
placeholder="请选择业务认责人员"
>
<el-option
v-for="dict in userList"
:key="dict.id"
:label="dict.userName"
:value="dict.userName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="技术认责部门" prop="dataStdItOwnershipDept">
<el-tree-select
v-model="formData.dataStdItOwnershipDept"
:data="deptOptions"
:props="{ value: 'label', label: 'label', children: 'children' }"
value-key="id"
:disabled="isShow"
placeholder="请选择技术认责部门"
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="技术认责人员" prop="dataStdItOwnershipPrsn">
<el-select
v-model="formData.dataStdItOwnershipPrsn"
:disabled="isShow"
placeholder="请选择技术认责人员"
>
<el-option
v-for="dict in userList"
:key="dict.id"
:label="dict.userName"
:value="dict.userName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 其余字段也按三列布局排布即可 --> <!-- 其余字段也按三列布局排布即可 -->
<el-row justify="center" style="margin-top: 20px;" v-if="!isShow"> <el-row class="action-row" justify="center" v-if="!isShow">
<el-col :span="4" style="padding-right: 10px;"> <el-col :span="4" class="action-col action-col-left">
<el-button @click="handleClose" style="width: 100%" plain>取消</el-button> <el-button @click="handleClose" class="action-btn" plain>取消</el-button>
</el-col> </el-col>
<el-col :span="4" style="padding-left: 10px;"> <el-col :span="4" class="action-col action-col-right">
<el-button type="primary" @click="handleSubmit" style="width: 100%">保存</el-button> <el-button type="primary" @click="handleSubmit" class="action-btn">保存</el-button>
</el-col> </el-col>
</el-row> </el-row>
</el-form> </el-form>
<el-dialog class="code-select-dialog" title="选择代码" v-model="codeVisible" width="1000px" append-to-body>
<code-components ref="showCodeDialog" v-if="codeVisible" />
<template #footer>
<div class="code-dialog-footer">
<el-button @click="codeVisible = false">取消</el-button>
<el-button type="primary" @click="handleCodeSelect">确定</el-button>
</div>
</template>
</el-dialog>
</el-dialog> </el-dialog>
</template> </template>
@ -353,3 +463,85 @@ const handleClose = () => {
// //
initData() initData()
</script> </script>
<style lang="scss" scoped>
.std-form-dialog {
:deep(.el-dialog__body) {
padding: 18px 22px 16px;
max-height: 72vh;
overflow-y: auto;
}
}
.std-form {
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-select),
:deep(.el-input),
:deep(.el-tree-select) {
width: 100%;
}
}
.action-row {
margin-top: 6px;
}
.action-col {
max-width: 140px;
}
.action-col-left {
padding-right: 10px;
}
.action-col-right {
padding-left: 10px;
}
.action-btn {
width: 100%;
}
.code-dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
@media (max-width: 992px) {
.action-col {
max-width: 180px;
}
}
@media (max-width: 768px) {
.std-form :deep(.el-row) {
margin-left: 0 !important;
margin-right: 0 !important;
}
.std-form :deep(.el-col) {
width: 100%;
max-width: 100%;
flex: 0 0 100%;
}
.action-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
}
.action-col,
.action-col-left,
.action-col-right {
max-width: 100%;
padding-left: 0;
padding-right: 0;
}
}
</style>

32
vue-fastapi-frontend/src/views/meta/metatask/index.vue

@ -500,13 +500,22 @@
</div> </div>
<el-form-item label="模式列表" prop="dbSName"> <el-form-item label="模式列表" prop="dbSName">
<el-input <el-select
v-model="form.dbSName" v-model="form.dbSName"
filterable
allow-create
clearable
placeholder="请输入模式列表,不填即采集全部的模式" placeholder="请输入模式列表,不填即采集全部的模式"
maxlength="30"
:disabled="isEdit" :disabled="isEdit"
style="width: 80%" style="width: 80%"
>
<el-option
v-for="item in dbSCodeList"
:key="item"
:label="item"
:value="item"
/> />
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -568,6 +577,7 @@
<script setup name="MetaTask"> <script setup name="MetaTask">
import { ref, reactive, onMounted, watch } from "vue"; import { ref, reactive, onMounted, watch } from "vue";
import { listmetatask, getmetatask, delmetatask, addmetatask, updatemetatask, downOrUpmetatask, datasourcetree, datasourceall ,dsmetataskdelete} from "@/api/meta/metatask"; import { listmetatask, getmetatask, delmetatask, addmetatask, updatemetatask, downOrUpmetatask, datasourcetree, datasourceall ,dsmetataskdelete} from "@/api/meta/metatask";
import { getschemaById} from "@/api/meta/metaInfo";
import runDialog from "./runDialog"; import runDialog from "./runDialog";
import dsDialog from "./dsDialog"; import dsDialog from "./dsDialog";
import logDialog from "./logsDialog"; import logDialog from "./logsDialog";
@ -595,10 +605,7 @@ const workerGroupList = ref(undefined);
const defindName = ref(""); const defindName = ref("");
const dbResourceOptions = ref(undefined); const dbResourceOptions = ref(undefined);
const dbRCodeList = ref(undefined); const dbRCodeList = ref(undefined);
const dbSCodeList = ref([ const dbSCodeList = ref([]);
{ value: "scheam", label: "scheam" },
{ value: "scheam2", label: "scheam2" }
]);
const metataskTypeOptions = ref([ const metataskTypeOptions = ref([
{ value: "0", label: "元数据采集", children: [{ value: "00", label: "表字段" }, { value: "01", label: "存储过程" }] }, { value: "0", label: "元数据采集", children: [{ value: "00", label: "表字段" }, { value: "01", label: "存储过程" }] },
{ value: "1", label: "元数据加工" } { value: "1", label: "元数据加工" }
@ -760,6 +767,17 @@ const reset = () => {
proxy.resetForm("taskForm"); proxy.resetForm("taskForm");
}; };
const loadSchemaList = async (dbRCode) => {
dbSCodeList.value = [];
if (!dbRCode) return;
try {
const response = await getschemaById(dbRCode);
dbSCodeList.value = response?.data || [];
} catch {
dbSCodeList.value = [];
}
};
const getIconClass = (data) => { const getIconClass = (data) => {
let icon="" let icon=""
if (data.parentId == '0') icon= 'Monitor'; if (data.parentId == '0') icon= 'Monitor';
@ -821,6 +839,7 @@ const handleAdd = () => {
return; return;
} }
reset(); reset();
loadSchemaList(clickNode.value?.id);
isEdit.value=false; isEdit.value=false;
open.value = true; open.value = true;
}; };
@ -830,6 +849,7 @@ const handleUpdate = async (row) => {
const metataskId = row.metataskId || ids.value; const metataskId = row.metataskId || ids.value;
const response = await getmetatask(metataskId); const response = await getmetatask(metataskId);
form.value = response.data; form.value = response.data;
await loadSchemaList(form.value.dbRCode);
open.value = true; open.value = true;
isEdit.value=true; isEdit.value=true;

Loading…
Cancel
Save