feat: 全量功能模块开发与集成测试修复

- 新增后端模块:Alert、APIAsset、Compliance、Lineage、Masking、Risk、SchemaChange、Unstructured、Watermark
- 新增前端模块页面与API接口
- 新增Alembic迁移脚本(002-014)覆盖全量业务表
- 新增测试数据生成脚本与集成测试脚本
- 修复metadata模型JSON类型导入缺失导致启动失败的问题
- 修复前端Alert/APIAsset页面request模块路径错误
- 更新docker-compose与开发计划文档
This commit is contained in:
hiderfong
2026-04-25 08:51:38 +08:00
parent 8b2bc84399
commit 6d70520e79
110 changed files with 6125 additions and 87 deletions
@@ -0,0 +1,143 @@
<template>
<div class="page-container">
<h2 class="page-title">非结构化文件识别</h2>
<div class="upload-area card-shadow" style="padding: 24px; margin-bottom: 16px;">
<el-upload
drag
action="#"
:auto-upload="false"
:on-change="handleFileChange"
accept=".docx,.xlsx,.pdf,.txt"
:limit="1"
>
<el-icon class="el-icon--upload"><upload-icon /></el-icon>
<div class="el-upload__text">拖拽文件到此处或 <em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip">支持 WordExcelPDFTXT 格式</div>
</template>
</el-upload>
<el-button type="primary" :loading="uploadLoading" @click="handleUpload" style="margin-top: 12px;">开始识别</el-button>
</div>
<div class="table-card card-shadow">
<el-table :data="fileList" v-loading="listLoading" stripe size="default" border>
<el-table-column prop="original_name" label="文件名" min-width="180" />
<el-table-column prop="file_type" label="类型" width="80" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 'processed' ? 'success' : row.status === 'error' ? 'danger' : 'warning'">
{{ ({ pending: '待处理', processed: '已处理', error: '失败' } as Record<string, string>)[row.status as string] || row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="敏感信息" min-width="200">
<template #default="{ row }">
<div v-if="row.analysis_result?.matches?.length">
<el-tag v-for="(m, idx) in row.analysis_result.matches.slice(0, 3)" :key="idx" size="small" type="danger" style="margin-right: 4px;">
{{ m.rule_name }}
</el-tag>
<span v-if="row.analysis_result.matches.length > 3" class="more-tag">+{{ row.analysis_result.matches.length - 3 }}</span>
</div>
<span v-else class="empty-text">未检测到</span>
</template>
</el-table-column>
<el-table-column label="操作" width="140" fixed="right">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="handleView(row)">查看详情</el-button>
<el-button type="primary" link size="small" @click="handleReprocess(row)">重新识别</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog v-model="detailVisible" title="识别详情" width="640px" destroy-on-close>
<div v-if="currentFile?.analysis_result?.matches?.length">
<div style="margin-bottom: 12px;">共发现 {{ currentFile.analysis_result.matches.length }} 处敏感信息</div>
<el-timeline>
<el-timeline-item v-for="(m, idx) in currentFile.analysis_result.matches" :key="idx" :type="m.level_code === 'L4' || m.level_code === 'L5' ? 'danger' : 'warning'">
<div style="font-weight: 600;">{{ m.rule_name }} ({{ m.level_code }})</div>
<div style="color: #909399; font-size: 13px; margin-top: 4px;">片段{{ m.snippet }}</div>
</el-timeline-item>
</el-timeline>
</div>
<el-empty v-else description="未检测到敏感信息" />
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Upload as UploadIcon } from '@element-plus/icons-vue'
import { uploadUnstructuredFile, getUnstructuredFiles, reprocessUnstructuredFile } from '@/api/unstructured'
const uploadLoading = ref(false)
const listLoading = ref(false)
const fileList = ref<any[]>([])
const selectedFile = ref<File | null>(null)
const detailVisible = ref(false)
const currentFile = ref<any>(null)
function handleFileChange(file: any) {
selectedFile.value = file.raw
}
async function handleUpload() {
if (!selectedFile.value) {
ElMessage.warning('请选择文件')
return
}
uploadLoading.value = true
try {
const res: any = await uploadUnstructuredFile(selectedFile.value)
ElMessage.success('上传并识别成功')
selectedFile.value = null
fetchList()
} catch (e: any) {
ElMessage.error(e?.message || '上传失败')
} finally {
uploadLoading.value = false
}
}
async function fetchList() {
listLoading.value = true
try {
const res: any = await getUnstructuredFiles()
fileList.value = res.data || []
} catch (e: any) {
ElMessage.error(e?.message || '加载失败')
} finally {
listLoading.value = false
}
}
function handleView(row: any) {
currentFile.value = row
detailVisible.value = true
}
async function handleReprocess(row: any) {
try {
const res: any = await reprocessUnstructuredFile(row.id)
ElMessage.success('重新识别完成')
fetchList()
} catch (e: any) {
ElMessage.error(e?.message || '识别失败')
}
}
onMounted(() => {
fetchList()
})
</script>
<style scoped lang="scss">
.empty-text {
color: #c0c4cc;
}
.more-tag {
font-size: 12px;
color: #909399;
}
</style>