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. 34
      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. 34
      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)
@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
)
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: 数据资产信息对象
"""
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()
return data_asset

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

@ -125,6 +125,11 @@ class DataAstDao:
await db.rollback()
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
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}")
main_model = DataAstInfoModel(**appr_model.model_dump(exclude_unset=True, by_alias=True))
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}")
# 更新 approStatus 状态

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

@ -65,6 +65,28 @@ class DataAstService:
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
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)

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

@ -56,7 +56,7 @@ class MetaService:
table['hasAsset'] = '1' # 已发布
else:
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['batchTabClas'] = CamelCaseUtil.transform_result(tab_list)
return meta_rel_list

34
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 typing import Set
from sqlparse.tokens import Keyword, DML
from urllib.parse import quote_plus
class MetaSecurityService:
"""
数据源安全管理模块服务层
@ -379,7 +380,9 @@ class MetaSecurityService:
# 1️⃣ 去掉 jdbc 前缀
jdbc_prefixes = {
"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():
if address.startswith(prefix):
@ -410,6 +413,21 @@ class MetaSecurityService:
pool_pre_ping=True,
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:
raise ValueError("不支持的数据库类型")
@ -438,7 +456,8 @@ class MetaSecurityService:
try:
async with async_session() as session:
await session.execute(text("SET statement_timeout = 30000"))
if (db_type or "").upper() == "POSTGRESQL":
await session.execute(text("SET statement_timeout = 30000"))
# ⭐ 原始数量
if sql_type == "原始结果":
count_sql = cls.build_count_sql(sql_query,db_type)
@ -474,6 +493,7 @@ class MetaSecurityService:
if select:
select.set("order", None)
select.set("limit", None)
select.set("offset", None)
cleaned_sql = parsed.sql(dialect=dialect)
return f"SELECT COUNT(*) AS cnt FROM ({cleaned_sql}) t"
@ -495,10 +515,18 @@ class MetaSecurityService:
elif db_type.lower() == "mysql":
# MySQL: 直接查询表字段(MySQL 没有 schema 的概念)
query = f"""
SELECT COLUMN_NAME
SELECT COLUMN_NAME AS column_name
FROM INFORMATION_SCHEMA.COLUMNS
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:
raise ValueError(f"暂不支持数据库类型: {db_type}")

1
vue-fastapi-backend/requirements.txt

@ -34,6 +34,7 @@ mdurl==0.1.2
minio==7.2.14
numpy==2.2.1
openpyxl==3.1.5
oracledb==2.2.1
orjson==3.10.12
packaging==25.0
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() {
return request({
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 cache from '@/plugins/cache'
// 查询参数列表
export function getDataSourceList(query) {
@ -100,7 +101,17 @@ export function getProcData(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>
<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 cache from "@/plugins/cache";
@ -520,7 +520,7 @@ const handleDelete = (row) => {
)
})
proxy.$modal.confirm('是否确认删除?').then(function () {
return batch({ dataAssets: submitasset });
return delAstAppr({ assetItems: submitasset });
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");

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

@ -1,5 +1,6 @@
<template>
<el-dialog
class="std-form-dialog"
width="900px"
append-to-body
:title="isShow ? '查看数据标准' : isEdit ? '修改数据标准' : '新增数据标准'"
@ -7,6 +8,7 @@
@close="handleClose"
>
<el-form
class="std-form"
:model="formData"
ref="formRef"
label-width="120px"
@ -168,17 +170,125 @@
</el-form-item>
</el-col>
</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-col :span="4" style="padding-right: 10px;">
<el-button @click="handleClose" style="width: 100%" plain>取消</el-button>
<el-row class="action-row" justify="center" v-if="!isShow">
<el-col :span="4" class="action-col action-col-left">
<el-button @click="handleClose" class="action-btn" plain>取消</el-button>
</el-col>
<el-col :span="4" style="padding-left: 10px;">
<el-button type="primary" @click="handleSubmit" style="width: 100%">保存</el-button>
<el-col :span="4" class="action-col action-col-right">
<el-button type="primary" @click="handleSubmit" class="action-btn">保存</el-button>
</el-col>
</el-row>
</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>
</template>
@ -353,3 +463,85 @@ const handleClose = () => {
//
initData()
</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>

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

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

Loading…
Cancel
Save