元数据审核处理接口 API 前端开发指南
本文档面向前端开发人员,用于开发"元数据审核处理"功能。
接口基本信息
| 属性 |
值 |
| 接口路径 |
POST /api/meta/review/resolve |
| 完整URL |
http://{host}:{port}/api/meta/review/resolve |
| Content-Type |
application/json |
| 请求方式 |
POST |
统一返回格式
成功响应
{
"code": 200,
"message": "操作成功",
"data": {
"id": 123,
"record_type": "redundancy",
"status": "resolved",
"resolution_action": "alias",
"resolution_payload": {
"primary_meta_id": 100,
"alias_meta_id": 200
},
"resolved_by": "admin",
"resolved_at": "2025-01-09T10:30:00.000000",
"notes": "合并重复元数据"
}
}
失败响应
{
"code": 500,
"message": "错误原因",
"data": null,
"error": "详细错误信息(可选)"
}
请求参数
公共参数
| 字段 |
类型 |
必填 |
说明 |
| id |
int |
是 |
审核记录ID |
| action |
string |
是 |
处理动作,可选值见下方说明 |
| payload |
object |
否 |
动作参数,根据action不同而变化 |
| resolved_by |
string |
否 |
处理人标识(建议传登录用户名/工号) |
| notes |
string |
否 |
处理备注 |
action 可选值
| action 值 |
用途 |
适用场景 |
alias |
设置元数据别名关系 |
redundancy(疑似冗余) |
create_new |
创建新元数据 |
redundancy(疑似冗余) |
accept_change |
接受元数据变动 |
change(疑似变动) |
reject_change |
拒绝元数据变动 |
change(疑似变动) |
ignore |
忽略该记录 |
任意类型 |
action=alias(设置元数据别名关系)
功能说明
在 Neo4j 的 DataMeta 节点之间建立 ALIAS 关系,用于合并重复/相似的元数据。
核心行为:
- 创建
(alias_meta)-[:ALIAS]->(primary_meta) 关系
- 将所有原先指向
alias_meta 的 ALIAS 关系转移到 primary_meta
primary_meta 已有的 ALIAS 关系保持不变
BusinessDomain 的 INCLUDES 关系不受影响
操作前:
[other_alias_1] --ALIAS--> [alias_meta]
[other_alias_2] --ALIAS--> [alias_meta]
[existing_alias] --ALIAS--> [primary_meta]
操作后:
[other_alias_1] --ALIAS--> [primary_meta]
[other_alias_2] --ALIAS--> [primary_meta]
[alias_meta] --ALIAS--> [primary_meta]
[existing_alias] --ALIAS--> [primary_meta]
payload 参数
| 字段 |
类型 |
必填 |
说明 |
| primary_meta_id |
int |
是 |
主元数据 Neo4j ID(作为别名目标) |
| alias_meta_id |
int |
是 |
别名元数据 Neo4j ID(将成为别名) |
请求示例
{
"id": 1001,
"action": "alias",
"payload": {
"primary_meta_id": 100,
"alias_meta_id": 200
},
"resolved_by": "admin",
"notes": "将重复元数据合并为别名"
}
响应示例
{
"code": 200,
"message": "操作成功",
"data": {
"id": 1001,
"record_type": "redundancy",
"business_domain_id": 345,
"new_meta": {
"name_zh": "科室名称",
"name_en": "DEPT_NAME",
"data_type": "varchar(50)"
},
"candidates": [...],
"status": "resolved",
"resolution_action": "alias",
"resolution_payload": {
"primary_meta_id": 100,
"alias_meta_id": 200
},
"resolved_by": "admin",
"resolved_at": "2025-01-09T10:30:00.000000",
"notes": "将重复元数据合并为别名",
"created_at": "2025-01-08T09:00:00.000000",
"updated_at": "2025-01-09T10:30:00.000000"
}
}
错误信息
| 错误信息 |
原因 |
payload.primary_meta_id 不能为空 |
未提供 primary_meta_id |
payload.alias_meta_id 不能为空 |
未提供 alias_meta_id |
primary_meta_id 和 alias_meta_id 不能相同 |
两个ID相同 |
action=create_new(创建新元数据)
payload 参数
| 字段 |
类型 |
必填 |
说明 |
| new_name_zh |
string |
是 |
新元数据中文名(需与现有区分) |
请求示例
{
"id": 1002,
"action": "create_new",
"payload": {
"new_name_zh": "HIS科室名称(新)"
},
"resolved_by": "admin"
}
错误信息
| 错误信息 |
原因 |
记录缺少 business_domain_id,无法执行 create_new |
审核记录无业务领域关联 |
payload.new_name_zh 不能为空 |
未提供中文名 |
创建新元数据失败 |
Neo4j 创建节点失败 |
action=accept_change(接受变动)
payload 参数
| 字段 |
类型 |
必填 |
说明 |
| meta_id |
int |
否 |
目标元数据ID,不传时使用 old_meta.meta_id |
请求示例
{
"id": 2001,
"action": "accept_change",
"payload": {
"meta_id": 789
},
"resolved_by": "admin",
"notes": "接受字段长度调整"
}
错误信息
| 错误信息 |
原因 |
无法确定需要更新的 meta_id |
未提供且记录中无 old_meta.meta_id |
action=reject_change(拒绝变动)
请求示例
{
"id": 2002,
"action": "reject_change",
"resolved_by": "admin",
"notes": "变动不合规,暂不更新"
}
action=ignore(忽略)
请求示例
{
"id": 3001,
"action": "ignore",
"resolved_by": "admin"
}
公共错误信息
| 错误信息 |
原因 |
请求数据格式错误,应为 JSON 对象 |
请求体不是有效的 JSON 对象 |
id 不能为空 |
未提供审核记录ID |
action 不能为空 |
未提供处理动作 |
记录不存在 |
审核记录ID不存在 |
记录已处理,无法重复处理 |
审核记录状态非 pending |
不支持的action: xxx |
action 值不在允许列表中 |
处理审核记录失败 |
服务器内部错误 |
Vue 示例代码
1. API 封装
// api/metaReview.js
import axios from 'axios'
const API_BASE = '/api/meta'
/**
* 处理审核记录
* @param {Object} params - 请求参数
* @param {number} params.id - 审核记录ID
* @param {string} params.action - 处理动作
* @param {Object} params.payload - 动作参数
* @param {string} params.resolved_by - 处理人
* @param {string} params.notes - 备注
* @returns {Promise}
*/
export function resolveReviewRecord(params) {
return axios.post(`${API_BASE}/review/resolve`, params)
}
/**
* 设置元数据别名关系
* @param {number} recordId - 审核记录ID
* @param {number} primaryMetaId - 主元数据ID
* @param {number} aliasMetaId - 别名元数据ID
* @param {string} resolvedBy - 处理人
* @param {string} notes - 备注
* @returns {Promise}
*/
export function setMetaAlias(recordId, primaryMetaId, aliasMetaId, resolvedBy, notes = '') {
return resolveReviewRecord({
id: recordId,
action: 'alias',
payload: {
primary_meta_id: primaryMetaId,
alias_meta_id: aliasMetaId
},
resolved_by: resolvedBy,
notes
})
}
/**
* 创建新元数据
* @param {number} recordId - 审核记录ID
* @param {string} newNameZh - 新元数据中文名
* @param {string} resolvedBy - 处理人
* @returns {Promise}
*/
export function createNewMeta(recordId, newNameZh, resolvedBy) {
return resolveReviewRecord({
id: recordId,
action: 'create_new',
payload: {
new_name_zh: newNameZh
},
resolved_by: resolvedBy
})
}
/**
* 接受元数据变动
* @param {number} recordId - 审核记录ID
* @param {number} metaId - 目标元数据ID(可选)
* @param {string} resolvedBy - 处理人
* @param {string} notes - 备注
* @returns {Promise}
*/
export function acceptChange(recordId, metaId, resolvedBy, notes = '') {
const payload = metaId ? { meta_id: metaId } : {}
return resolveReviewRecord({
id: recordId,
action: 'accept_change',
payload,
resolved_by: resolvedBy,
notes
})
}
/**
* 拒绝元数据变动
* @param {number} recordId - 审核记录ID
* @param {string} resolvedBy - 处理人
* @param {string} notes - 备注
* @returns {Promise}
*/
export function rejectChange(recordId, resolvedBy, notes = '') {
return resolveReviewRecord({
id: recordId,
action: 'reject_change',
resolved_by: resolvedBy,
notes
})
}
/**
* 忽略审核记录
* @param {number} recordId - 审核记录ID
* @param {string} resolvedBy - 处理人
* @returns {Promise}
*/
export function ignoreRecord(recordId, resolvedBy) {
return resolveReviewRecord({
id: recordId,
action: 'ignore',
resolved_by: resolvedBy
})
}
2. 设置别名组件
<template>
<div class="alias-dialog">
<el-dialog
title="设置元数据别名"
:visible.sync="dialogVisible"
width="600px"
@close="handleClose"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
>
<el-form-item label="主元数据" prop="primaryMetaId">
<el-select
v-model="form.primaryMetaId"
placeholder="请选择主元数据"
filterable
style="width: 100%"
>
<el-option
v-for="item in candidateList"
:key="item.candidate_meta_id"
:label="`${item.name_zh} (${item.name_en || '-'})`"
:value="item.candidate_meta_id"
/>
</el-select>
<div class="form-tip">选择作为主元数据的节点,其他节点将成为它的别名</div>
</el-form-item>
<el-form-item label="别名元数据" prop="aliasMetaId">
<el-select
v-model="form.aliasMetaId"
placeholder="请选择别名元数据"
filterable
style="width: 100%"
>
<el-option
v-for="item in candidateList"
:key="item.candidate_meta_id"
:label="`${item.name_zh} (${item.name_en || '-'})`"
:value="item.candidate_meta_id"
:disabled="item.candidate_meta_id === form.primaryMetaId"
/>
</el-select>
<div class="form-tip">选择要降级为别名的元数据节点</div>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="form.notes"
type="textarea"
:rows="3"
placeholder="请输入处理备注(可选)"
/>
</el-form-item>
</el-form>
<el-alert
v-if="form.aliasMetaId"
type="warning"
:closable="false"
style="margin-top: 16px"
>
<template #title>
<span>操作提示</span>
</template>
<div>
<p>执行此操作后:</p>
<ul>
<li>别名元数据将指向主元数据</li>
<li>原先指向别名元数据的所有 ALIAS 关系将转移到主元数据</li>
<li>业务领域的 INCLUDES 关系不受影响</li>
</ul>
</div>
</el-alert>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button
type="primary"
:loading="loading"
@click="handleSubmit"
>
确定
</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { setMetaAlias } from '@/api/metaReview'
export default {
name: 'AliasDialog',
props: {
visible: {
type: Boolean,
default: false
},
recordId: {
type: Number,
required: true
},
candidateList: {
type: Array,
default: () => []
}
},
data() {
return {
loading: false,
form: {
primaryMetaId: null,
aliasMetaId: null,
notes: ''
},
rules: {
primaryMetaId: [
{ required: true, message: '请选择主元数据', trigger: 'change' }
],
aliasMetaId: [
{ required: true, message: '请选择别名元数据', trigger: 'change' },
{
validator: (rule, value, callback) => {
if (value && value === this.form.primaryMetaId) {
callback(new Error('主元数据和别名元数据不能相同'))
} else {
callback()
}
},
trigger: 'change'
}
]
}
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
methods: {
handleClose() {
this.$refs.formRef?.resetFields()
this.form = {
primaryMetaId: null,
aliasMetaId: null,
notes: ''
}
this.dialogVisible = false
},
async handleSubmit() {
try {
await this.$refs.formRef.validate()
} catch {
return
}
this.loading = true
try {
const currentUser = this.$store.getters.userInfo?.username || 'unknown'
const res = await setMetaAlias(
this.recordId,
this.form.primaryMetaId,
this.form.aliasMetaId,
currentUser,
this.form.notes
)
if (res.data.code === 200) {
this.$message.success('别名关系设置成功')
this.$emit('success', res.data.data)
this.handleClose()
} else {
this.$message.error(res.data.message || '操作失败')
}
} catch (error) {
console.error('设置别名失败:', error)
this.$message.error(error.response?.data?.message || '网络错误,请稍后重试')
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.form-tip {
color: #909399;
font-size: 12px;
line-height: 1.5;
margin-top: 4px;
}
</style>
3. 审核详情页面示例
<template>
<div class="review-detail">
<el-card>
<template #header>
<div class="card-header">
<span>审核记录详情 #{{ record.id }}</span>
<el-tag :type="statusTagType">{{ statusText }}</el-tag>
</div>
</template>
<!-- 记录基本信息 -->
<el-descriptions :column="2" border>
<el-descriptions-item label="记录类型">
{{ record.record_type === 'redundancy' ? '疑似冗余' : '疑似变动' }}
</el-descriptions-item>
<el-descriptions-item label="业务领域ID">
{{ record.business_domain_id }}
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ record.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间">
{{ record.updated_at }}
</el-descriptions-item>
</el-descriptions>
<!-- 新元数据信息 -->
<h4>新解析元数据</h4>
<el-descriptions :column="2" border>
<el-descriptions-item label="中文名">
{{ record.new_meta?.name_zh }}
</el-descriptions-item>
<el-descriptions-item label="英文名">
{{ record.new_meta?.name_en }}
</el-descriptions-item>
<el-descriptions-item label="数据类型">
{{ record.new_meta?.data_type }}
</el-descriptions-item>
</el-descriptions>
<!-- 冗余场景:候选列表 -->
<template v-if="record.record_type === 'redundancy'">
<h4>候选元数据列表</h4>
<el-table :data="record.candidates" border>
<el-table-column prop="candidate_meta_id" label="ID" width="80" />
<el-table-column prop="name_zh" label="中文名" />
<el-table-column prop="name_en" label="英文名" />
<el-table-column prop="data_type" label="数据类型" />
<el-table-column label="差异字段">
<template #default="{ row }">
<el-tag
v-for="field in row.diff_fields"
:key="field"
size="small"
style="margin-right: 4px"
>
{{ field }}
</el-tag>
</template>
</el-table-column>
</el-table>
</template>
<!-- 操作按钮 -->
<div v-if="record.status === 'pending'" class="action-buttons">
<template v-if="record.record_type === 'redundancy'">
<el-button type="primary" @click="showAliasDialog = true">
设为别名
</el-button>
<el-button type="success" @click="showCreateDialog = true">
创建新元数据
</el-button>
</template>
<template v-else>
<el-button type="primary" @click="handleAcceptChange">
接受变动
</el-button>
<el-button type="warning" @click="handleRejectChange">
拒绝变动
</el-button>
</template>
<el-button @click="handleIgnore">忽略</el-button>
</div>
</el-card>
<!-- 别名设置弹窗 -->
<alias-dialog
:visible.sync="showAliasDialog"
:record-id="record.id"
:candidate-list="record.candidates"
@success="handleSuccess"
/>
</div>
</template>
<script>
import AliasDialog from './AliasDialog.vue'
import { acceptChange, rejectChange, ignoreRecord } from '@/api/metaReview'
export default {
name: 'ReviewDetail',
components: { AliasDialog },
props: {
record: {
type: Object,
required: true
}
},
data() {
return {
showAliasDialog: false,
showCreateDialog: false
}
},
computed: {
statusTagType() {
const map = {
pending: 'warning',
resolved: 'success',
ignored: 'info'
}
return map[this.record.status] || 'info'
},
statusText() {
const map = {
pending: '待处理',
resolved: '已处理',
ignored: '已忽略'
}
return map[this.record.status] || this.record.status
}
},
methods: {
getCurrentUser() {
return this.$store.getters.userInfo?.username || 'unknown'
},
async handleAcceptChange() {
try {
await this.$confirm('确定接受此变动吗?', '提示', {
type: 'warning'
})
const res = await acceptChange(
this.record.id,
this.record.old_meta?.meta_id,
this.getCurrentUser()
)
if (res.data.code === 200) {
this.$message.success('操作成功')
this.$emit('refresh')
} else {
this.$message.error(res.data.message)
}
} catch (e) {
if (e !== 'cancel') {
this.$message.error('操作失败')
}
}
},
async handleRejectChange() {
try {
await this.$confirm('确定拒绝此变动吗?', '提示', {
type: 'warning'
})
const res = await rejectChange(this.record.id, this.getCurrentUser())
if (res.data.code === 200) {
this.$message.success('操作成功')
this.$emit('refresh')
} else {
this.$message.error(res.data.message)
}
} catch (e) {
if (e !== 'cancel') {
this.$message.error('操作失败')
}
}
},
async handleIgnore() {
try {
await this.$confirm('确定忽略此记录吗?', '提示', {
type: 'warning'
})
const res = await ignoreRecord(this.record.id, this.getCurrentUser())
if (res.data.code === 200) {
this.$message.success('操作成功')
this.$emit('refresh')
} else {
this.$message.error(res.data.message)
}
} catch (e) {
if (e !== 'cancel') {
this.$message.error('操作失败')
}
}
},
handleSuccess() {
this.$emit('refresh')
}
}
}
</script>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
h4 {
margin: 20px 0 10px;
color: #303133;
}
.action-buttons {
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #ebeef5;
}
.action-buttons .el-button {
margin-right: 12px;
}
</style>
注意事项
状态限制:只有 status=pending 的记录才能处理,已处理记录会返回错误。
参数验证:前端应在提交前验证必填参数,避免无效请求。
别名操作不可逆:alias 操作会修改 Neo4j 中的关系结构,请确保用户确认后再执行。
ID 类型:所有 ID 参数(record_id、primary_meta_id、alias_meta_id、meta_id)应为整数。
错误处理:建议统一封装 axios 拦截器处理错误响应,对 code !== 200 的情况进行统一提示。
更新日志
| 日期 |
版本 |
更新内容 |
| 2025-01-09 |
v2.0 |
action=alias 参数变更:使用 primary_meta_id 和 alias_meta_id 替代原 candidate_meta_id,支持 ALIAS 关系重建 |