Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
M
model_analysis
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
model_analysis
Commits
5b9d1940
Commit
5b9d1940
authored
Aug 27, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 接入模型监控数据源并加强审批校验
parent
41e4f24d
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
79 additions
and
62 deletions
+79
-62
.umirc.ts
.umirc.ts
+1
-0
model-monitor-source-fixtures.sql
scripts/mysql/model-monitor-source-fixtures.sql
+0
-0
ForwardApprovals.jsx
src/pages/project_manager_xy/ForwardApprovals.jsx
+38
-49
ModelMonitorConnectorService.js
src/pages/project_manager_xy/ModelMonitorConnectorService.js
+0
-0
model-monitor.business-flow.md
src/pages/project_manager_xy/model-monitor.business-flow.md
+9
-6
modelMonitorService.js
src/pages/project_manager_xy/modelMonitorService.js
+8
-2
DataService.js
src/services/DataService.js
+15
-0
record-api.md
src/services/record-api.md
+4
-3
subjectsDefinition.md
src/services/subjectsDefinition.md
+4
-2
No files found.
.umirc.ts
View file @
5b9d1940
...
@@ -133,5 +133,6 @@ export default defineConfig({
...
@@ -133,5 +133,6 @@ export default defineConfig({
define
:
{
define
:
{
STREAMS_API_URL
:
'/api'
,
STREAMS_API_URL
:
'/api'
,
STREAMS_PARSER_URL
:
'/parser'
,
STREAMS_PARSER_URL
:
'/parser'
,
MODEL_MONITOR_CONNECTOR_NAME
:
process
.
env
.
MODEL_MONITOR_CONNECTOR_NAME
||
'MODEL_MONITOR_ORACLE'
,
},
},
});
});
scripts/mysql/model-monitor-source-fixtures.sql
0 → 100644
View file @
5b9d1940
This diff is collapsed.
Click to expand it.
src/pages/project_manager_xy/ForwardApprovals.jsx
View file @
5b9d1940
...
@@ -47,6 +47,27 @@ function canRecipientEdit(record, currentUserId) {
...
@@ -47,6 +47,27 @@ function canRecipientEdit(record, currentUserId) {
);
);
}
}
function
canFirstApprove
(
record
,
currentUserId
)
{
return
Boolean
(
record
?.
firstApprover
===
currentUserId
&&
record
.
editorStatus
===
'completed'
&&
record
.
approvalStatus
===
'pending'
,
);
}
function
canFinalApprove
(
record
,
currentUserId
)
{
return
Boolean
(
record
?.
finalApprover
===
currentUserId
&&
record
.
editorStatus
===
'completed'
&&
record
.
approvalStatus
===
'approved'
&&
record
.
finalApprovalStatus
===
'pending'
,
);
}
function
canApproveCurrentStep
(
record
,
currentUserId
)
{
return
canFirstApprove
(
record
,
currentUserId
)
||
canFinalApprove
(
record
,
currentUserId
);
}
function
getForwardEditorStatusLabel
(
record
)
{
function
getForwardEditorStatusLabel
(
record
)
{
return
isReturnedForEditing
(
record
)
?
'待重新填写'
:
getEditorStatusLabel
(
record
?.
editorStatus
);
return
isReturnedForEditing
(
record
)
?
'待重新填写'
:
getEditorStatusLabel
(
record
?.
editorStatus
);
}
}
...
@@ -125,15 +146,7 @@ export default function ForwardApprovals() {
...
@@ -125,15 +146,7 @@ export default function ForwardApprovals() {
}
}
if
(
activeFilter
===
'pending'
)
{
if
(
activeFilter
===
'pending'
)
{
return
(
return
canApproveCurrentStep
(
record
,
currentUserId
);
(
record
.
firstApprover
===
currentUserId
&&
record
.
editorStatus
===
'completed'
&&
record
.
approvalStatus
===
'pending'
)
||
(
record
.
finalApprover
===
currentUserId
&&
record
.
editorStatus
===
'completed'
&&
record
.
approvalStatus
===
'approved'
&&
record
.
finalApprovalStatus
===
'pending'
)
);
}
}
return
(
return
(
...
@@ -226,7 +239,8 @@ export default function ForwardApprovals() {
...
@@ -226,7 +239,8 @@ export default function ForwardApprovals() {
}
}
async
function
handleSaveEditableRecords
()
{
async
function
handleSaveEditableRecords
()
{
if
(
!
editingRecord
)
{
if
(
!
canRecipientEdit
(
editingRecord
,
currentUserId
))
{
setError
(
'当前用户不是可填写接收人,或该转发记录已完成填写。'
);
return
;
return
;
}
}
...
@@ -248,7 +262,8 @@ export default function ForwardApprovals() {
...
@@ -248,7 +262,8 @@ export default function ForwardApprovals() {
}
}
async
function
handleCompleteEditing
()
{
async
function
handleCompleteEditing
()
{
if
(
!
editingRecord
)
{
if
(
!
canRecipientEdit
(
editingRecord
,
currentUserId
))
{
setError
(
'当前用户不是可填写接收人,或该转发记录已完成填写。'
);
return
;
return
;
}
}
...
@@ -292,30 +307,21 @@ export default function ForwardApprovals() {
...
@@ -292,30 +307,21 @@ export default function ForwardApprovals() {
}
}
async
function
handleApprove
(
record
)
{
async
function
handleApprove
(
record
)
{
if
(
if
(
canFirstApprove
(
record
,
currentUserId
))
{
record
.
firstApprover
===
currentUserId
&&
record
.
editorStatus
===
'completed'
&&
record
.
approvalStatus
===
'pending'
)
{
await
handleUpdateRecord
(
record
,
{
approval_status
:
'approved'
});
await
handleUpdateRecord
(
record
,
{
approval_status
:
'approved'
});
return
;
return
;
}
}
if
(
if
(
canFinalApprove
(
record
,
currentUserId
))
{
record
.
finalApprover
===
currentUserId
&&
record
.
approvalStatus
===
'approved'
&&
record
.
finalApprovalStatus
===
'pending'
)
{
await
handleUpdateRecord
(
record
,
{
final_approval_status
:
'approved'
});
await
handleUpdateRecord
(
record
,
{
final_approval_status
:
'approved'
});
return
;
}
}
setError
(
'当前用户无权审批该环节,或审批状态已经变化。'
);
}
}
async
function
handleReject
(
record
)
{
async
function
handleReject
(
record
)
{
if
(
if
(
canFirstApprove
(
record
,
currentUserId
))
{
record
.
firstApprover
===
currentUserId
&&
record
.
editorStatus
===
'completed'
&&
record
.
approvalStatus
===
'pending'
)
{
await
handleUpdateRecord
(
record
,
{
await
handleUpdateRecord
(
record
,
{
editor_status
:
'editing'
,
editor_status
:
'editing'
,
completed_by_id
:
''
,
completed_by_id
:
''
,
...
@@ -327,11 +333,7 @@ export default function ForwardApprovals() {
...
@@ -327,11 +333,7 @@ export default function ForwardApprovals() {
return
;
return
;
}
}
if
(
if
(
canFinalApprove
(
record
,
currentUserId
))
{
record
.
finalApprover
===
currentUserId
&&
record
.
approvalStatus
===
'approved'
&&
record
.
finalApprovalStatus
===
'pending'
)
{
await
handleUpdateRecord
(
record
,
{
await
handleUpdateRecord
(
record
,
{
editor_status
:
'editing'
,
editor_status
:
'editing'
,
completed_by_id
:
''
,
completed_by_id
:
''
,
...
@@ -339,7 +341,10 @@ export default function ForwardApprovals() {
...
@@ -339,7 +341,10 @@ export default function ForwardApprovals() {
completed_at
:
''
,
completed_at
:
''
,
final_approval_status
:
'rejected'
,
final_approval_status
:
'rejected'
,
});
});
return
;
}
}
setError
(
'当前用户无权审批该环节,或审批状态已经变化。'
);
}
}
const
canCurrentUserEdit
=
Boolean
(
const
canCurrentUserEdit
=
Boolean
(
...
@@ -481,15 +486,7 @@ export default function ForwardApprovals() {
...
@@ -481,15 +486,7 @@ export default function ForwardApprovals() {
<
Button
<
Button
isDisabled=
{
isDisabled=
{
saving
||
saving
||
!
(
!
canApproveCurrentStep
(
activeRecord
,
currentUserId
)
(
activeRecord
.
firstApprover
===
currentUserId
&&
activeRecord
.
editorStatus
===
'completed'
&&
activeRecord
.
approvalStatus
===
'pending'
)
||
(
activeRecord
.
finalApprover
===
currentUserId
&&
activeRecord
.
editorStatus
===
'completed'
&&
activeRecord
.
approvalStatus
===
'approved'
&&
activeRecord
.
finalApprovalStatus
===
'pending'
)
)
}
}
variant=
"danger-soft"
variant=
"danger-soft"
onPress=
{
()
=>
handleReject
(
activeRecord
)
}
onPress=
{
()
=>
handleReject
(
activeRecord
)
}
...
@@ -499,15 +496,7 @@ export default function ForwardApprovals() {
...
@@ -499,15 +496,7 @@ export default function ForwardApprovals() {
<
Button
<
Button
isDisabled=
{
isDisabled=
{
saving
||
saving
||
!
(
!
canApproveCurrentStep
(
activeRecord
,
currentUserId
)
(
activeRecord
.
firstApprover
===
currentUserId
&&
activeRecord
.
editorStatus
===
'completed'
&&
activeRecord
.
approvalStatus
===
'pending'
)
||
(
activeRecord
.
finalApprover
===
currentUserId
&&
activeRecord
.
editorStatus
===
'completed'
&&
activeRecord
.
approvalStatus
===
'approved'
&&
activeRecord
.
finalApprovalStatus
===
'pending'
)
)
}
}
variant=
"primary"
variant=
"primary"
onPress=
{
()
=>
handleApprove
(
activeRecord
)
}
onPress=
{
()
=>
handleApprove
(
activeRecord
)
}
...
...
src/pages/project_manager_xy/ModelMonitorConnectorService.js
0 → 100644
View file @
5b9d1940
This diff is collapsed.
Click to expand it.
src/pages/project_manager_xy/model-monitor.business-flow.md
View file @
5b9d1940
...
@@ -30,7 +30,7 @@
...
@@ -30,7 +30,7 @@
## 3. 外部数据源契约
## 3. 外部数据源契约
当前外部来源仍使用 Mock 适配器,但返回结构已映射到
`ANALYZE_MXVIEW_INFO`
和
`ANALYZE_MODEL_RUN_BATCH_INFO`
。UI 仅依赖以下方法,真实 Oracle 适配器接入时不得修改页面调用
:
生产环境的外部来源通过通用 Connector 接口读取固定 Oracle 数据源,开发联调允许使用遵循同一表结构的 MySQL 5.7+ 测试源;适配器根据 Connector 的
`settings.client`
使用 Oracle 双引号或 MySQL 反引号引用动态表与字段。同步后的模型监控数据写入平台普通 Subject。模型监控页面不提供数据源选择器,来源 connector 名称由构建常量
`MODEL_MONITOR_CONNECTOR_NAME`
提供。UI 仅依赖以下方法,页面调用保持不变
:
```
ts
```
ts
listSourceModels
():
Promise
<
Array
<
{
listSourceModels
():
Promise
<
Array
<
{
...
@@ -56,11 +56,13 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
...
@@ -56,11 +56,13 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
}
>
;
}
>
;
```
```
来源映射规则:模型稳定键为
`ANALYZE_MXVIEW_INFO.MODEL_NAME + ID`
;批次稳定键为
`ANALYZE_MODEL_RUN_BATCH_INFO.MODEL_RUN_BATCH_ID`
,直接映射到本地
`batch_code`
;结果表来自
`MODEL_DATA_TABLE_NAME`
。结果表查询必须先对白名单表名做校验,再
使用绑定参数
按
`MODEL_RUN_BATCH_ID`
过滤,不能整表读取。
来源映射规则:模型稳定键为
`ANALYZE_MXVIEW_INFO.MODEL_NAME + ID`
;批次稳定键为
`ANALYZE_MODEL_RUN_BATCH_INFO.MODEL_RUN_BATCH_ID`
,直接映射到本地
`batch_code`
;结果表来自
`MODEL_DATA_TABLE_NAME`
。结果表查询必须先对白名单表名做校验,再
对批次 ID 做字符串转义并
按
`MODEL_RUN_BATCH_ID`
过滤,不能整表读取。
`ANALYZE_MODEL_DATA_MX_*`
结果行不要求业务主键。每个批次只允许同步一次,结果行按来源读取顺序记录
`source_row_number`
并纯新增,不进行去重或更新。
`ANALYZE_MODEL_DATA_MX_*`
结果行不要求业务主键。每个批次只允许同步一次,结果行按来源读取顺序记录
`source_row_number`
并纯新增,不进行去重或更新。
未来真实后台接口到位时只替换适配器,不修改页面和本地持久化服务。仓库目前没有 Oracle Connector 的调用协议,接入前必须补充接口 URL、请求体、分页和字段类型响应定义。
MySQL 联调数据可通过
`scripts/mysql/model-monitor-source-fixtures.sql`
初始化。脚本只创建不存在的来源表,并按稳定键更新 3 个模型、9 个批次和 48 条结果样例;使用测试源启动前端时,必须把
`MODEL_MONITOR_CONNECTOR_NAME`
配置为对应的 MySQL Connector。该环境只验证通用同步流程,不替代 Oracle 驱动和方言的集成验收。
未来若固定数据源配置变化,只替换构建常量和适配器实现,不修改页面和本地持久化服务。
## 4. 核心流程
## 4. 核心流程
...
@@ -84,8 +86,8 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
...
@@ -84,8 +86,8 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
-
来源声明数量与实际读取数量检查
-
来源声明数量与实际读取数量检查
-
字段别名、重复字段和类型检查
-
字段别名、重复字段和类型检查
-
所有数值、日期、布尔和对象字段的规范化检查
-
所有数值、日期、布尔和对象字段的规范化检查
8.
本地没有模型时按
`MODEL_NAME + ID`
创建稳定
`model_monitor_model`
Record,并生成稳定动态数据 Subject 名称。
8.
本地没有模型时按
`MODEL_NAME + ID`
创建稳定
`model_monitor_model`
Record,并生成
以
`model_monitor_data_`
开头的
稳定动态数据 Subject 名称。
9.
自动创建或扩展
模型数据 Subject。只允许增加字段;
类型冲突立即停止。
9.
自动创建或扩展
普通模型数据 Subject。只允许增加字段,
类型冲突立即停止。
10.
按
`MODEL_RUN_BATCH_ID`
查找批次;任何状态的本地批次一旦存在都拒绝重复同步,不再读取来源结果表。
10.
按
`MODEL_RUN_BATCH_ID`
查找批次;任何状态的本地批次一旦存在都拒绝重复同步,不再读取来源结果表。
11.
为来源行生成从 1 开始的
`source_row_number`
。
11.
为来源行生成从 1 开始的
`source_row_number`
。
12.
每 20 条分块纯新增写入,不查询已有结果行,也不执行更新;数据 Record name 由
`MODEL_RUN_BATCH_ID + source_row_number`
生成。
12.
每 20 条分块纯新增写入,不查询已有结果行,也不执行更新;数据 Record name 由
`MODEL_RUN_BATCH_ID + source_row_number`
生成。
...
@@ -98,7 +100,7 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
...
@@ -98,7 +100,7 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
-
`partial`
:部分行写入失败,成功数据保留。
-
`partial`
:部分行写入失败,成功数据保留。
-
`failed`
:校验、对象创建或同步过程失败。
-
`failed`
:校验、对象创建或同步过程失败。
-
`partial/failed`
批次保留现场供排查,同样不允许重试,避免重复写入。
-
`partial/failed`
批次保留现场供排查,同样不允许重试,避免重复写入。
-
批次保留错误摘要、新增数、失败数、来源行数和本地总数;更新数固定为 0。
-
批次保留错误摘要、新增数、失败数、来源行数和本地总数;更新数固定为 0。
模型监控 Record 写入失败时保留已成功写入的数据和批次状态。
### 4.4 模型分组
### 4.4 模型分组
...
@@ -142,6 +144,7 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
...
@@ -142,6 +144,7 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
6.
任一审批驳回后,填写状态恢复为
`editing`
,清空上次完成人和完成时间,接收人可修改原回填内容。
6.
任一审批驳回后,填写状态恢复为
`editing`
,清空上次完成人和完成时间,接收人可修改原回填内容。
7.
接收人重新完成填写时,
`approval_status`
和
`final_approval_status`
均重置为
`pending`
,流程从一级审批重新开始。
7.
接收人重新完成填写时,
`approval_status`
和
`final_approval_status`
均重置为
`pending`
,流程从一级审批重新开始。
8.
终审通过后状态为已最终审批,流程结束。
8.
终审通过后状态为已最终审批,流程结束。
9.
接收人填写、完成填写、一级审批和终审在前端同时执行按钮禁用与提交事件校验;用户身份或流程状态不满足时不发送更新请求,并提示状态已经变化或当前用户无权操作。
## 5. 页面状态与异常处理
## 5. 页面状态与异常处理
...
...
src/pages/project_manager_xy/modelMonitorService.js
View file @
5b9d1940
...
@@ -16,7 +16,7 @@ import {
...
@@ -16,7 +16,7 @@ import {
listSourceBatches
,
listSourceBatches
,
listSourceModels
,
listSourceModels
,
loadSourceBatchData
,
loadSourceBatchData
,
}
from
'./ModelMonitor
Mock
Service'
;
}
from
'./ModelMonitor
Connector
Service'
;
export
const
MODEL_GROUP_SUBJECT
=
'model_monitor_group'
;
export
const
MODEL_GROUP_SUBJECT
=
'model_monitor_group'
;
export
const
MODEL_SUBJECT
=
'model_monitor_model'
;
export
const
MODEL_SUBJECT
=
'model_monitor_model'
;
...
@@ -302,6 +302,7 @@ function normalizeSubject(subject = {}) {
...
@@ -302,6 +302,7 @@ function normalizeSubject(subject = {}) {
id
:
candidate
?.
_id
||
candidate
?.
id
||
candidate
?.
name
||
''
,
id
:
candidate
?.
_id
||
candidate
?.
id
||
candidate
?.
name
||
''
,
name
:
candidate
?.
name
||
candidate
?.
title
||
''
,
name
:
candidate
?.
name
||
candidate
?.
title
||
''
,
title
:
candidate
?.
title
||
candidate
?.
name
||
''
,
title
:
candidate
?.
title
||
candidate
?.
name
||
''
,
type
:
candidate
?.
type
||
'normal'
,
fields
:
(
Array
.
isArray
(
fields
)
?
fields
:
[]).
map
(
normalizeField
),
fields
:
(
Array
.
isArray
(
fields
)
?
fields
:
[]).
map
(
normalizeField
),
};
};
}
}
...
@@ -495,7 +496,7 @@ function validateSourceSelection(sourceModel = {}, sourceBatch = {}) {
...
@@ -495,7 +496,7 @@ function validateSourceSelection(sourceModel = {}, sourceBatch = {}) {
throw
new
Error
(
'来源批次的 MODEL_NAME 与所选模型名称不一致。'
);
throw
new
Error
(
'来源批次的 MODEL_NAME 与所选模型名称不一致。'
);
}
}
if
(
!
/^
[
A-Z
][
A-Z0-9_$#
]
*
(\.[
A-Z
][
A-Z0-9_$#
]
*
)?
$/
.
test
(
sourceDataTableName
))
{
if
(
!
/^
[
A-Z
][
A-Z0-9_$#
]
*
(\.[
A-Z
][
A-Z0-9_$#
]
*
)?
$/
.
test
(
sourceDataTableName
))
{
throw
new
Error
(
'来源批次的数据表名为空或不符合
Oracle 非引号
标识符规范。'
);
throw
new
Error
(
'来源批次的数据表名为空或不符合
来源数据库
标识符规范。'
);
}
}
return
{
return
{
...
@@ -570,6 +571,11 @@ async function ensureSubject(definition) {
...
@@ -570,6 +571,11 @@ async function ensureSubject(definition) {
return
normalizeSubject
(
await
createSubject
(
toSubjectPayload
(
definition
)));
return
normalizeSubject
(
await
createSubject
(
toSubjectPayload
(
definition
)));
}
}
const
expectedType
=
String
(
definition
.
type
||
'normal'
).
toLowerCase
();
if
(
String
(
subject
.
type
||
'normal'
).
toLowerCase
()
!==
expectedType
)
{
throw
new
Error
(
`对象“
${
definition
.
name
}
”当前类型为
${
subject
.
type
}
,期望类型为
${
expectedType
}
,已停止初始化。`
);
}
const
existingFields
=
new
Map
(
subject
.
fields
.
map
((
field
)
=>
[
field
.
alias
,
field
]));
const
existingFields
=
new
Map
(
subject
.
fields
.
map
((
field
)
=>
[
field
.
alias
,
field
]));
for
(
const
field
of
definition
.
fields
)
{
for
(
const
field
of
definition
.
fields
)
{
const
existing
=
existingFields
.
get
(
field
.
name
);
const
existing
=
existingFields
.
get
(
field
.
name
);
...
...
src/services/DataService.js
View file @
5b9d1940
...
@@ -1339,6 +1339,19 @@ function connectorTest(data) {
...
@@ -1339,6 +1339,19 @@ function connectorTest(data) {
});
});
}
}
function
querySources
(
query
)
{
return
request
(
'/connector/manage/query'
,
{
method
:
'post'
,
data
:
query
,
});
}
function
getSource
(
name
)
{
return
request
(
`/connector/manage/
${
name
}
`
,
{
method
:
'get'
,
});
}
function
connectorList
(
data
)
{
function
connectorList
(
data
)
{
return
request
(
`/connector/list`
,
{
return
request
(
`/connector/list`
,
{
method
:
'post'
,
method
:
'post'
,
...
@@ -2080,6 +2093,7 @@ export {
...
@@ -2080,6 +2093,7 @@ export {
connectorExecute
,
connectorExecute
,
connectorInspect
,
connectorInspect
,
connectorList
,
connectorList
,
getSource
,
connectorQuery
,
connectorQuery
,
connectorQueryExecute
,
connectorQueryExecute
,
connectorSelect
,
connectorSelect
,
...
@@ -2243,6 +2257,7 @@ export {
...
@@ -2243,6 +2257,7 @@ export {
// Notice
// Notice
queryNotice
,
queryNotice
,
queryPlaywrightSession
,
queryPlaywrightSession
,
querySources
,
queryRecords
,
queryRecords
,
queryRemovedRecord
,
queryRemovedRecord
,
//rule
//rule
...
...
src/services/record-api.md
View file @
5b9d1940
...
@@ -44,7 +44,7 @@
...
@@ -44,7 +44,7 @@
}
}
```
```
开发阶段用于自动创建固定
`model_monitor_*`
Subject 和每模型动态数据 Subject。正式环境建议由部署流程预建固定 Subject。
开发阶段用于自动创建固定
`model_monitor_*`
Subject 和每模型动态数据 Subject。
当前均使用后端默认的
`normal`
类型,不传
`type`
和
`external`
;对象名不附加数据库类型前缀,
正式环境建议由部署流程预建固定 Subject。
### 2.3 增加 Subject 字段
### 2.3 增加 Subject 字段
...
@@ -158,7 +158,7 @@
...
@@ -158,7 +158,7 @@
## 5. 外部来源适配契约
## 5. 外部来源适配契约
当前仓库
尚未提供 Oracle Connector 的请求体和响应体协议,因此仍由
`ModelMonitorMockService`
提供数据,但 Mock 已按
`ANALYZE_MXVIEW_INFO`
和
`ANALYZE_MODEL_RUN_BATCH_INFO`
的字段语义返回。真实适配器接入后必须保持以下签名,页面和 Record 同步逻辑不直接拼 SQL
:
当前仓库
通过通用 Connector 接口读取生产 Oracle 数据源;开发联调允许接入遵循相同来源表结构的 MySQL 5.7+ 测试数据源。同步后的模型、批次和动态数据写入平台普通 Subject。模型监控页面不直接拼 SQL,也不让用户在页面上选择数据源;来源 connector 名称由构建常量
`MODEL_MONITOR_CONNECTOR_NAME`
提供。前端通过
`/connector/manage/:name`
读取来源连接配置,再用
`/connector/execute`
与
`/connector/inspect`
读取模型、批次和批次数据;动态标识符必须根据 Connector 客户端使用对应方言引用。接口适配层必须保持以下签名
:
```
ts
```
ts
listSourceModels
():
Promise
<
Array
<
{
listSourceModels
():
Promise
<
Array
<
{
...
@@ -204,13 +204,14 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
...
@@ -204,13 +204,14 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
-
`batch_code`
直接保存
`ANALYZE_MODEL_RUN_BATCH_INFO.MODEL_RUN_BATCH_ID`
,不再增加重复的来源跑批 ID 字段。
-
`batch_code`
直接保存
`ANALYZE_MODEL_RUN_BATCH_INFO.MODEL_RUN_BATCH_ID`
,不再增加重复的来源跑批 ID 字段。
-
批次的
`MODEL_NAME + MODEL_ID`
必须等于所选模型主键。
-
批次的
`MODEL_NAME + MODEL_ID`
必须等于所选模型主键。
-
`MODEL_DATA_TABLE_NAME`
只能来自批次表,并必须匹配 Oracle 非引号标识符白名单;不得接受页面输入的任意表名。
-
`MODEL_DATA_TABLE_NAME`
只能来自批次表,并必须匹配 Oracle 非引号标识符白名单;不得接受页面输入的任意表名。
-
动态表名不能作为绑定参数,
后端必须先校验白名单再拼接;批次 ID 必须使用绑定参数
查询。
-
动态表名不能作为绑定参数,
必须先校验白名单再拼接;批次 ID 需要在适配层做字符串转义后再参与
查询。
-
结果表必须按
`MODEL_RUN_BATCH_ID`
过滤,禁止只按表名读取整张模型结果表。
-
结果表必须按
`MODEL_RUN_BATCH_ID`
过滤,禁止只按表名读取整张模型结果表。
-
`source_model_data_count`
存在时必须与实际读取行数一致,否则停止写入。
-
`source_model_data_count`
存在时必须与实际读取行数一致,否则停止写入。
-
本地一旦存在相同
`batch_code`
的批次 Record,无论状态为何,界面和 service 层都必须拒绝再次同步。
-
本地一旦存在相同
`batch_code`
的批次 Record,无论状态为何,界面和 service 层都必须拒绝再次同步。
-
`columns[].name`
必须匹配
`^[a-z][a-z0-9_]*$`
且不可重复。
-
`columns[].name`
必须匹配
`^[a-z][a-z0-9_]*$`
且不可重复。
-
日期在写入 Record 前转换为 ISO 8601。
-
日期在写入 Record 前转换为 ISO 8601。
-
非法字段值或字段类型冲突必须在写入前报错。
-
非法字段值或字段类型冲突必须在写入前报错。
-
所有模型监控 Subject 当前均使用默认
`normal`
类型,数据写入平台内部存储;对象名统一使用
`model_monitor_`
前缀,不附加数据库类型前缀。
-
结果行只执行新增,不读取已有结果行,也不执行更新;
`source_row_number`
保存本次来源读取顺序。
-
结果行只执行新增,不读取已有结果行,也不执行更新;
`source_row_number`
保存本次来源读取顺序。
-
模型、批次和数据行 Record name 分别由
`MODEL_NAME + ID`
、
`MODEL_RUN_BATCH_ID`
、
`MODEL_RUN_BATCH_ID + source_row_number`
生成稳定哈希。
-
模型、批次和数据行 Record name 分别由
`MODEL_NAME + ID`
、
`MODEL_RUN_BATCH_ID`
、
`MODEL_RUN_BATCH_ID + source_row_number`
生成稳定哈希。
...
...
src/services/subjectsDefinition.md
View file @
5b9d1940
...
@@ -15,6 +15,8 @@
...
@@ -15,6 +15,8 @@
旧
`analysis_view_config`
、
`analysis_view_group`
、
`analysis_forward_record`
、
`analysis_forward_record_item`
、
`analysis_dashboard_board`
仅作为历史数据保留,新界面不得读取或写入。
旧
`analysis_view_config`
、
`analysis_view_group`
、
`analysis_forward_record`
、
`analysis_forward_record_item`
、
`analysis_dashboard_board`
仅作为历史数据保留,新界面不得读取或写入。
当前阶段所有模型监控 Subject 均使用默认
`normal`
类型,Record 保存在平台内部存储中;MySQL 仅作为模型来源数据源。对象名统一使用
`model_monitor_`
前缀,不附加数据库类型前缀。
## 1. 统一 Record 协议
## 1. 统一 Record 协议
```
ts
```
ts
...
@@ -147,7 +149,7 @@ type Record = {
...
@@ -147,7 +149,7 @@ type Record = {
## 3. 动态模型数据 Subject
## 3. 动态模型数据 Subject
每个模型首次同步时按
`ANALYZE_MXVIEW_INFO.MODEL_NAME + ID`
自动创建稳定的
动态数据 Subject。
后续批次继续使用同一 Subject。
每个模型首次同步时按
`ANALYZE_MXVIEW_INFO.MODEL_NAME + ID`
自动创建稳定的
`model_monitor_data_<stable_id>`
普通动态数据 Subject,
后续批次继续使用同一 Subject。
固定系统字段:
固定系统字段:
...
@@ -170,6 +172,6 @@ type Record = {
...
@@ -170,6 +172,6 @@ type Record = {
-
模型和批次不提供删除。
-
模型和批次不提供删除。
-
模型仅由同步自动创建,来源模型 ID、模型编码和名称不可本地修改。
-
模型仅由同步自动创建,来源模型 ID、模型编码和名称不可本地修改。
-
`hidden_record_ids`
为批次级共享状态,与转发的
`excluded_record_ids`
分开维护。
-
`hidden_record_ids`
为批次级共享状态,与转发的
`excluded_record_ids`
分开维护。
-
固定 Subject 在开发阶段由前端检查并创建;正式环境应由部署流程预建。
-
固定 Subject 在开发阶段由前端检查并创建
为普通对象
;正式环境应由部署流程预建。
-
已存在的 Subject 自动补充新字段时先按可选字段添加,以兼容历史 Record;新同步数据仍由 service 层执行必填校验。
-
已存在的 Subject 自动补充新字段时先按可选字段添加,以兼容历史 Record;新同步数据仍由 service 层执行必填校验。
-
Subject/Record 写入失败必须明确报错,不得回退 localStorage。
-
Subject/Record 写入失败必须明确报错,不得回退 localStorage。
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment