chore: refresh public mirror
This commit is contained in:
@@ -392,3 +392,16 @@
|
|||||||
### Documentation
|
### Documentation
|
||||||
- Documented `AD_TABLE.MASK` as the table's declared business-action codes: `A` add, `M` modify, `D` delete, `Q` query, `U` cancel submission, `V` void, and `S` submit.
|
- Documented `AD_TABLE.MASK` as the table's declared business-action codes: `A` add, `M` modify, `D` delete, `Q` query, `U` cancel submission, `V` void, and `S` submit.
|
||||||
- Restored Git/Gitea as the only Skill update source; removed the OSS and `skill_update` release/update instructions.
|
- Restored Git/Gitea as the only Skill update source; removed the OSS and `skill_update` release/update instructions.
|
||||||
|
## 1.5.38 (2026-08-14)
|
||||||
|
|
||||||
|
### Trusted device login
|
||||||
|
- Added local Ed25519 key generation and `device-key.json` protection.
|
||||||
|
- Added `device_register` and `device_login` commands for the normal pending/approved trusted-device flow.
|
||||||
|
- Raw MAC values are never sent or stored; only a SHA-256 hash and masked hint are submitted.
|
||||||
|
- Authorization testing passed; included in the private repository release.
|
||||||
|
## 1.5.39 (2026-08-14)
|
||||||
|
|
||||||
|
### Trusted-device token renewal
|
||||||
|
- `ensure_logged_in()` now automatically obtains a new transit token with the approved local device when the token is missing or expired.
|
||||||
|
- Falls back to the normal secret-key login prompt when trusted-device renewal is unavailable.
|
||||||
|
- Authorization testing passed; included in the private repository release.
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ description: Oracle 跳板查询技能。通过中转服务查询远程 Oracle
|
|||||||
生产查询限制、商品/款号/条码/库存规则、新增业务记录 SQL 生成规则、字段值域映射。
|
生产查询限制、商品/款号/条码/库存规则、新增业务记录 SQL 生成规则、字段值域映射。
|
||||||
- [BOS 数据字典与系统地图](./references/bos-metadata.md)
|
- [BOS 数据字典与系统地图](./references/bos-metadata.md)
|
||||||
`AD_TABLE`、`AD_COLUMN`、`AD_REFBYTABLE`、`AD_TABLE_TEXT`、BOS 系统菜单和表结构地图、数据权限模型。
|
`AD_TABLE`、`AD_COLUMN`、`AD_REFBYTABLE`、`AD_TABLE_TEXT`、BOS 系统菜单和表结构地图、数据权限模型。
|
||||||
|
- [通用接口业务逻辑](./references/generic-interface.md)
|
||||||
|
`H_INTERFACE`、`H_INSTRUCTION`、`PUSH_LOG`、`INF_LOG` 的通用职责、入站/出站流程、常见字段、状态核验、分析步骤和风险检查;处理不同客户服务器时必须以目标环境的实际表结构、BOS 元数据、值域和过程源码为准。
|
||||||
- [表空间、服务器体检与 Oracle 运维监控](./references/oracle-ops-and-checkup.md)
|
- [表空间、服务器体检与 Oracle 运维监控](./references/oracle-ops-and-checkup.md)
|
||||||
表空间、服务器体检、Oracle 运维报告、SYS/当前账号函数授权方案和 AWR 未来开发计划。
|
表空间、服务器体检、Oracle 运维报告、SYS/当前账号函数授权方案和 AWR 未来开发计划。
|
||||||
- [整理前完整 SKILL.md 备份](./references/legacy-skill-v1.5.8.md)
|
- [整理前完整 SKILL.md 备份](./references/legacy-skill-v1.5.8.md)
|
||||||
@@ -53,6 +55,8 @@ python scripts/oracle_skill.py login <secretKey> [clientCode]
|
|||||||
|------|------|
|
|------|------|
|
||||||
| `status` | 查看登录状态、当前 client 和 Agent 状态 |
|
| `status` | 查看登录状态、当前 client 和 Agent 状态 |
|
||||||
| `clients` | 无感刷新当前用户最新授权的 client 列表(只使用当前登录 token) |
|
| `clients` | 无感刷新当前用户最新授权的 client 列表(只使用当前登录 token) |
|
||||||
|
| `device_register [deviceName]` | 在本机生成 Ed25519 密钥并提交可信设备注册请求;后台审批前状态为 `PENDING` |
|
||||||
|
| `device_login [clientCode]` | 使用本机密钥完成 challenge/signature 可信设备登录;仅限后台已审批设备 |
|
||||||
| `switch <clientCode|clientName>` | 按 code 或名称切换当前 client;找不到时自动刷新 client 列表 |
|
| `switch <clientCode|clientName>` | 按 code 或名称切换当前 client;找不到时自动刷新 client 列表 |
|
||||||
| `analyze <schema> <proc>` | 完整分析存储过程 |
|
| `analyze <schema> <proc>` | 完整分析存储过程 |
|
||||||
| `source <schema> <proc>` | 获取存储过程源码 |
|
| `source <schema> <proc>` | 获取存储过程源码 |
|
||||||
@@ -102,6 +106,7 @@ AD_SUBSYSTEM
|
|||||||
|
|
||||||
## 业务规则速记
|
## 业务规则速记
|
||||||
|
|
||||||
|
- 用户提到“通用接口”“接口指令”“接口方”“推送日志”“接收日志”或要求分析 `H_INTERFACE`、`H_INSTRUCTION`、`PUSH_LOG`、`INF_LOG` 时,先读取 [通用接口业务逻辑](./references/generic-interface.md)。这四张表是常见模型而不是固定契约;不同服务器可能缺表、增表、增减字段、改变值域或使用不同处理过程,必须先查目标环境再下结论。
|
||||||
- 商品、款号通常指 `M_PRODUCT`。
|
- 商品、款号通常指 `M_PRODUCT`。
|
||||||
- 条码、SKU 通常指 `M_PRODUCT_ALIAS`;查询条码时首查 `M_PRODUCT_ALIAS.NO`。
|
- 条码、SKU 通常指 `M_PRODUCT_ALIAS`;查询条码时首查 `M_PRODUCT_ALIAS.NO`。
|
||||||
- 款号和条码是一对多关系。
|
- 款号和条码是一对多关系。
|
||||||
|
|||||||
@@ -0,0 +1,412 @@
|
|||||||
|
# 通用接口业务逻辑
|
||||||
|
|
||||||
|
## 业务名称和适用范围
|
||||||
|
|
||||||
|
本业务统一称为:**通用接口**。
|
||||||
|
|
||||||
|
通用接口通常由以下四张核心表组成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
H_INTERFACE
|
||||||
|
H_INSTRUCTION
|
||||||
|
PUSH_LOG
|
||||||
|
INF_LOG
|
||||||
|
```
|
||||||
|
|
||||||
|
这四张表描述的是常见接口框架,不是所有客户服务器都完全一致的固定数据库契约。不同环境可能出现:
|
||||||
|
|
||||||
|
- 某张表不存在,或存在额外的接口方明细、IP 白名单、请求头、参数映射等子表。
|
||||||
|
- 字段数量、字段类型、默认值、是否可空、索引和约束不同。
|
||||||
|
- 状态值、限定值组、入站入口函数、出站任务和业务处理过程不同。
|
||||||
|
- 只有入站接口、只有出站接口,或者入站和出站同时存在。
|
||||||
|
- `H_INSTRUCTION_ID`、`H_INTERFACE_ID` 只有逻辑关联,没有物理外键。
|
||||||
|
|
||||||
|
因此分析通用接口时,必须先查询目标服务器的实际结构、BOS 元数据、限定值和过程源码,不得直接套用其他客户环境的接口名称、过程名、字段列表、状态含义、数据量或结论。
|
||||||
|
|
||||||
|
## 四表职责
|
||||||
|
|
||||||
|
| 表 | 通用职责 | 数据性质 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `H_INTERFACE` | 定义外部接口方、目标地址和通用回执处理方式 | 接口方配置表 |
|
||||||
|
| `H_INSTRUCTION` | 定义接口指令、传输方向、处理过程、调度、重试和启停规则 | 核心指令/路由配置表 |
|
||||||
|
| `PUSH_LOG` | 保存待发送数据,并记录出站推送、重试和处理结果 | 出站队列兼日志表 |
|
||||||
|
| `INF_LOG` | 保存外部传入请求,并记录入站处理状态和返回结果 | 入站请求兼日志表 |
|
||||||
|
|
||||||
|
可以用下面的业务问题快速理解四表分工:
|
||||||
|
|
||||||
|
```text
|
||||||
|
H_INTERFACE:数据发给谁?目标地址和公共回执如何处理?
|
||||||
|
H_INSTRUCTION:发什么或收什么?由哪个过程处理?何时执行?
|
||||||
|
PUSH_LOG:准备发出去什么?是否已经发送和处理成功?
|
||||||
|
INF_LOG:外部传进来什么?本地是否处理成功?返回了什么?
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见关系
|
||||||
|
|
||||||
|
```text
|
||||||
|
H_INTERFACE(接口方)
|
||||||
|
1
|
||||||
|
|
|
||||||
|
| 常见关联字段 H_INSTRUCTION.H_INTERFACE_ID
|
||||||
|
n
|
||||||
|
H_INSTRUCTION(接口指令)
|
||||||
|
1 1
|
||||||
|
| |
|
||||||
|
| H_INSTRUCTION_ID | H_INSTRUCTION_ID
|
||||||
|
n n
|
||||||
|
PUSH_LOG(出站记录) INF_LOG(入站记录)
|
||||||
|
```
|
||||||
|
|
||||||
|
常见但必须核验的关系:
|
||||||
|
|
||||||
|
- 出站指令通常通过 `H_INSTRUCTION.H_INTERFACE_ID` 指向 `H_INTERFACE.ID`。
|
||||||
|
- `PUSH_LOG.H_INSTRUCTION_ID` 通常指向产生或处理该推送记录的指令。
|
||||||
|
- `INF_LOG.H_INSTRUCTION_ID` 通常指向匹配并处理该入站请求的指令。
|
||||||
|
- `PUSH_LOG.TYPE`、`INF_LOG.TYPE` 可能保存 `H_INSTRUCTION.CODE`,也可能保存其他接口类型标识。
|
||||||
|
- `H_INTERFACE` 可能通过 `AD_REFBYTABLE` 关联接口方明细表,用于保存路径、请求方式、请求头、参数或其他分项配置。
|
||||||
|
|
||||||
|
不要只根据同名字段认定存在强制关系。必须同时检查:
|
||||||
|
|
||||||
|
- `AD_REFBYTABLE` 的 BOS 逻辑关系。
|
||||||
|
- `ALL_CONSTRAINTS` / `ALL_CONS_COLUMNS` 的物理外键。
|
||||||
|
- 存储过程源码中的查询和写入条件。
|
||||||
|
|
||||||
|
## H_INTERFACE:接口方配置
|
||||||
|
|
||||||
|
### 业务职责
|
||||||
|
|
||||||
|
`H_INTERFACE` 用于描述一个外部系统或接口目标,常见于出站推送场景。它通常保存接口名称、地址、公共回执解析方式和启用状态,本身不保存具体待推送业务数据。
|
||||||
|
|
||||||
|
### 常见字段族
|
||||||
|
|
||||||
|
| 常见字段 | 常见含义 | 核验要求 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ID` | 接口方主键 | 核验主键和生成方式 |
|
||||||
|
| `NAME` | 接口名称或接口方名称 | 不同环境可能有编码字段 |
|
||||||
|
| `URL` | 基础地址或目标地址 | 可能拆到明细表或指令表 |
|
||||||
|
| `SETPCDE` | 通用回执解析或后处理过程 | 必须查源码确认签名和作用 |
|
||||||
|
| `DESCRIPTION` | 备注 | 不能作为程序判断依据 |
|
||||||
|
| `ISACTIVE` | 是否可用 | 必须查限定值,不直接假定 Y/N |
|
||||||
|
| `AD_CLIENT_ID`、`AD_ORG_ID` | 公司、组织 | 检查是否硬编码或从配置继承 |
|
||||||
|
| 创建人、修改人、创建时间、修改时间 | 审计字段 | 字段名可能不同 |
|
||||||
|
|
||||||
|
### 判断原则
|
||||||
|
|
||||||
|
- `H_INTERFACE` 为空不一定是异常。如果目标环境只有入站接口,可能根本不需要接口方记录。
|
||||||
|
- 有出站指令时,如果接口地址不在 `H_INTERFACE`,需要继续检查接口方明细表、`H_INSTRUCTION` 或过程代码中的地址来源。
|
||||||
|
- 不要仅看到 `URL` 就认为它是完整请求地址;它可能只是基础地址或逻辑来源标识。
|
||||||
|
|
||||||
|
## H_INSTRUCTION:接口指令和路由
|
||||||
|
|
||||||
|
### 业务职责
|
||||||
|
|
||||||
|
`H_INSTRUCTION` 是通用接口的核心配置表。它定义接口指令编号、传输方向、入站处理过程、出站生成过程、调度规则、优先级、重试次数、启用状态和日志保留参数。
|
||||||
|
|
||||||
|
### 常见字段族
|
||||||
|
|
||||||
|
| 常见字段 | 常见含义 | 核验要求 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ID` | 指令主键 | 核验主键和生成方式 |
|
||||||
|
| `CODE` | 外部或内部使用的指令编号 | 检查是否唯一、是否区分大小写 |
|
||||||
|
| `NAME` | 指令名称 | 只用于显示,不替代 CODE |
|
||||||
|
| `INOUT` | 传输方向 | 常见为 `IN`/`OUT`,必须查询限定值 |
|
||||||
|
| `SETPCDE` | 入站触发处理过程 | 查源码、参数签名、异常处理和影响表 |
|
||||||
|
| `PCDE_PUSH` | 出站数据生成或推送准备过程 | 查源码确认是否产生 `PUSH_LOG` |
|
||||||
|
| `H_INTERFACE_ID` | 出站目标接口方 | 入站指令可能为空 |
|
||||||
|
| `TIMERULES` | 即时、定时或间隔规则 | 必须查询限定值 |
|
||||||
|
| `TIMES_H`、`TIMES_M` | 定时执行时分 | 字段可能不存在或改名 |
|
||||||
|
| `SETTIMES`、`SETTIMES_S`、`TIMESCOUNT` | 间隔调度参数 | 核对单位和计时更新任务 |
|
||||||
|
| `ISRUN`、`ISACTIVE` | 是否运行、是否有效 | 查询限定值和实际过滤条件 |
|
||||||
|
| `IS_READY` | 调度就绪或运行中状态 | 状态集合以任务过程源码为准 |
|
||||||
|
| `NUM_REPUSH` | 允许或默认重试次数 | 核对是配置上限还是当前次数 |
|
||||||
|
| `PRIORITY` | 执行优先级 | 核对数字大/小谁优先 |
|
||||||
|
| `IS_THROWING` | 是否立即向调用方抛错 | 查入口函数和处理过程如何使用 |
|
||||||
|
| `IS_IPTEST` | 是否启用来源 IP 校验 | 同时检查白名单表和网络层控制 |
|
||||||
|
| `IS_DATA` | 是否执行数据处理 | 语义以过程源码为准 |
|
||||||
|
| `DELQTY` | 常见为日志保留天数 | 必须验证是否真的存在清理任务 |
|
||||||
|
| `MSG_ERROR` | 最近一次任务错误或执行信息 | 可能被后续执行覆盖,不等于完整历史 |
|
||||||
|
|
||||||
|
### 入站配置的常见约束
|
||||||
|
|
||||||
|
- `INOUT` 为入站时,通常必须配置 `SETPCDE`。
|
||||||
|
- 接口方、推送过程和调度字段通常可以为空。
|
||||||
|
- 入站入口函数通常按 `CODE` 或 `TYPE` 查找启用指令,然后调用 `SETPCDE`。
|
||||||
|
|
||||||
|
### 出站配置的常见约束
|
||||||
|
|
||||||
|
- `INOUT` 为出站时,通常需要接口方或可解析出的目标地址。
|
||||||
|
- 非即时任务通常需要 `PCDE_PUSH` 和有效调度参数。
|
||||||
|
- 定时或间隔任务可能通过 `IS_READY`、`TIMESCOUNT` 等字段协调执行。
|
||||||
|
|
||||||
|
### 分析重点
|
||||||
|
|
||||||
|
- `CODE` 是否有唯一约束;如果没有,先查询是否存在重复配置。
|
||||||
|
- 新增后、修改后是否配置 `TRIG_AC`、`TRIG_AM` 进行配置校验。
|
||||||
|
- 入口函数匹配 `CODE` 时是否统一大小写,未知指令是否返回明确错误。
|
||||||
|
- 动态调用过程名是否来自 `SETPCDE` / `PCDE_PUSH`,谁有权限修改这些字段。
|
||||||
|
- `DELQTY` 是否只是配置,还是确实被清理任务读取。
|
||||||
|
|
||||||
|
## PUSH_LOG:出站队列和日志
|
||||||
|
|
||||||
|
### 业务职责
|
||||||
|
|
||||||
|
`PUSH_LOG` 用于保存待推送到外部系统的数据,并记录同步状态、本地处理状态、重试次数、目标地址、业务关键字、请求内容和外部响应。它通常既是待处理队列,也是历史日志。
|
||||||
|
|
||||||
|
### 常见字段族
|
||||||
|
|
||||||
|
| 常见字段 | 常见含义 | 核验要求 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ID` | 推送记录主键 | 核验生成方式 |
|
||||||
|
| `H_INSTRUCTION_ID` | 对应出站指令 | 检查物理/逻辑关系 |
|
||||||
|
| `NAME`、`TYPE` | 接口或业务类型 | 以生成过程为准 |
|
||||||
|
| `URL` | 实际请求地址 | 可能由接口方配置拼接产生 |
|
||||||
|
| `JSON`、`JSON_BEFORE` | 待推送、转换前或转换后内容 | 必须查写入源码确认区别 |
|
||||||
|
| `TBSTATUS` | 传输/同步状态 | 查询限定值和任务过程 |
|
||||||
|
| `DOSTATUS` | 本地处理状态 | 不要与 TBSTATUS 混用 |
|
||||||
|
| `NUM_REPUSH` | 已重试次数或剩余次数 | 以更新逻辑为准 |
|
||||||
|
| `DOCNO` | 单号或搜索标识 | 可能为空或存脱敏/哈希标识 |
|
||||||
|
| `MSG`、`REMARK` | 处理结果或外部响应 | 可能包含敏感信息 |
|
||||||
|
| `CREATIONDATE`、`MODIFIEDDATE` | 创建、更新时间 | 用于队列时效和历史分析 |
|
||||||
|
|
||||||
|
### 常见状态参考
|
||||||
|
|
||||||
|
部分环境中常见:
|
||||||
|
|
||||||
|
```text
|
||||||
|
TBSTATUS:1 未同步、0 同步中、2 同步成功、3 同步失败
|
||||||
|
DOSTATUS:1 未处理、2 处理成功、3 处理失败
|
||||||
|
```
|
||||||
|
|
||||||
|
这只是常见参考,不是跨服务器固定契约。实际分析时必须从 `AD_COLUMN.OBTAINMANNER`、`AD_LIMITVALUE_GROUP_ID`、`AD_LIMITVALUE` 和任务过程源码核实。
|
||||||
|
|
||||||
|
### 出站流程
|
||||||
|
|
||||||
|
```text
|
||||||
|
业务过程或定时任务
|
||||||
|
-> 根据 H_INSTRUCTION 产生待推送数据
|
||||||
|
-> 写入 PUSH_LOG
|
||||||
|
-> 推送任务筛选待同步记录
|
||||||
|
-> 根据 H_INSTRUCTION / H_INTERFACE 取得处理过程和目标地址
|
||||||
|
-> 调用外部接口
|
||||||
|
-> 更新 TBSTATUS、DOSTATUS、NUM_REPUSH、MSG/REMARK
|
||||||
|
-> 成功结束,或按规则重试/转人工处理
|
||||||
|
```
|
||||||
|
|
||||||
|
`PUSH_LOG` 为空时,先检查是否存在启用的出站指令和数据生成过程,再判断是“按设计未使用”还是“出站链路没有产生日志”。
|
||||||
|
|
||||||
|
## INF_LOG:入站请求和处理日志
|
||||||
|
|
||||||
|
### 业务职责
|
||||||
|
|
||||||
|
`INF_LOG` 用于接收外部系统传入的数据,保存来源、指令类型、请求内容、处理状态和返回信息。入口函数通常先写日志,再调用指令配置的处理过程,最后把结果更新回同一条日志。
|
||||||
|
|
||||||
|
### 常见字段族
|
||||||
|
|
||||||
|
| 常见字段 | 常见含义 | 核验要求 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ID` | 入站日志主键 | 核验生成方式 |
|
||||||
|
| `H_INSTRUCTION_ID` | 匹配到的入站指令 | 检查是否存在孤儿记录 |
|
||||||
|
| `TYPE` | 外部传入的指令或类型 | 核对是否等于 `H_INSTRUCTION.CODE` |
|
||||||
|
| `URL` | 来源地址、来源 IP 或请求 URL | 字段名不能证明具体语义,查入口参数 |
|
||||||
|
| `JSON`、`JSON_BEFORE` | 原始或规范化后的请求内容 | 查写入顺序,避免错误假定 |
|
||||||
|
| `DOSTATUS` | 本地处理状态 | 查询限定值和入口函数源码 |
|
||||||
|
| `MSG` | 处理结果或返回内容 | 失败消息可能掩盖真实异常 |
|
||||||
|
| `DOCNO` | 单号或业务关键字 | 可能未填充;注意敏感信息保护 |
|
||||||
|
| `CREATIONDATE` | 接收时间 | 用于时效、流量和保留期分析 |
|
||||||
|
|
||||||
|
### 入站流程
|
||||||
|
|
||||||
|
```text
|
||||||
|
外部请求
|
||||||
|
-> 入站入口函数/过程
|
||||||
|
-> 根据 TYPE 或 CODE 查 H_INSTRUCTION
|
||||||
|
-> 写 INF_LOG,常见初始状态为未处理
|
||||||
|
-> 调用 H_INSTRUCTION.SETPCDE
|
||||||
|
-> 处理成功:更新为成功并保存返回内容
|
||||||
|
-> 处理失败:更新为失败并保存业务错误或技术错误
|
||||||
|
-> 将结果返回调用方
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分析重点
|
||||||
|
|
||||||
|
- 是否存在长时间停留在“未处理”的积压记录。
|
||||||
|
- 失败是业务拒绝还是系统异常;不能只根据 `DOSTATUS` 汇总。
|
||||||
|
- 处理过程是否把 `WHEN OTHERS` 统一包装成业务错误,导致根因丢失。
|
||||||
|
- `JSON` 与 `JSON_BEFORE` 是否真的分别保存原始报文和转换后报文。
|
||||||
|
- `DOCNO` 是否实际填充;如果为空,相关复合索引可能无法发挥预期作用。
|
||||||
|
- 来源地址是否单一、是否启用 IP 白名单、网络层是否有等效控制。
|
||||||
|
- 请求内容、响应信息是否包含个人信息、凭证、令牌或其他敏感数据。
|
||||||
|
|
||||||
|
## 标准分析流程
|
||||||
|
|
||||||
|
用户要求分析“通用接口”或这四张表时,按以下顺序执行。
|
||||||
|
|
||||||
|
### 1. 确认目标环境
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/oracle_skill.py status
|
||||||
|
python scripts/oracle_skill.py switch <clientCode|clientName>
|
||||||
|
```
|
||||||
|
|
||||||
|
必须确认当前 client、Agent 在线状态和授权状态。不要把一个客户的结构、接口配置或结论复制到另一个客户。
|
||||||
|
|
||||||
|
### 2. 确认实际 schema 和表结构
|
||||||
|
|
||||||
|
依次执行 `describe`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/oracle_skill.py describe <schema> H_INTERFACE
|
||||||
|
python scripts/oracle_skill.py describe <schema> H_INSTRUCTION
|
||||||
|
python scripts/oracle_skill.py describe <schema> PUSH_LOG
|
||||||
|
python scripts/oracle_skill.py describe <schema> INF_LOG
|
||||||
|
```
|
||||||
|
|
||||||
|
如果指定 schema 不正确,使用命令返回的实际 owner,并继续核验 `ALL_TAB_COLUMNS`。不要因为某环境常用某个 schema 就硬编码 owner。
|
||||||
|
|
||||||
|
通用物理字段查询模板:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT OWNER,
|
||||||
|
TABLE_NAME,
|
||||||
|
COLUMN_ID,
|
||||||
|
COLUMN_NAME,
|
||||||
|
DATA_TYPE,
|
||||||
|
DATA_LENGTH,
|
||||||
|
DATA_PRECISION,
|
||||||
|
DATA_SCALE,
|
||||||
|
NULLABLE,
|
||||||
|
DATA_DEFAULT
|
||||||
|
FROM ALL_TAB_COLUMNS
|
||||||
|
WHERE OWNER = '<ACTUAL_OWNER>'
|
||||||
|
AND TABLE_NAME IN ('H_INTERFACE', 'H_INSTRUCTION', 'PUSH_LOG', 'INF_LOG')
|
||||||
|
AND ROWNUM <= 300
|
||||||
|
ORDER BY TABLE_NAME, COLUMN_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 查询 BOS 元数据和字段值域
|
||||||
|
|
||||||
|
先确认目标环境 `AD_TABLE` 的实际字段,再查询四表的业务说明、表单事件过程和字段元数据。不同版本的 `AD_TABLE` 可能没有相同的扩展列,遇到 `ORA-00904` 时应先 `describe AD_TABLE`,不要反复套用固定列名。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT ID,
|
||||||
|
NAME,
|
||||||
|
DESCRIPTION,
|
||||||
|
PROC_SUBMIT,
|
||||||
|
HAS_TRIG_BD,
|
||||||
|
TRIG_BD,
|
||||||
|
HAS_TRIG_AC,
|
||||||
|
TRIG_AC,
|
||||||
|
HAS_TRIG_AM,
|
||||||
|
TRIG_AM
|
||||||
|
FROM AD_TABLE
|
||||||
|
WHERE UPPER(NAME) IN ('H_INTERFACE', 'H_INSTRUCTION', 'PUSH_LOG', 'INF_LOG')
|
||||||
|
AND ROWNUM <= 20
|
||||||
|
```
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT t.NAME AS TABLE_NAME,
|
||||||
|
c.ORDERNO,
|
||||||
|
c.DBNAME,
|
||||||
|
c.NAME AS COLUMN_NAME,
|
||||||
|
c.DESCRIPTION,
|
||||||
|
c.COLTYPE,
|
||||||
|
c.OBTAINMANNER,
|
||||||
|
c.AD_LIMITVALUE_GROUP_ID
|
||||||
|
FROM AD_TABLE t
|
||||||
|
JOIN AD_COLUMN c ON c.AD_TABLE_ID = t.ID
|
||||||
|
WHERE UPPER(t.NAME) IN ('H_INTERFACE', 'H_INSTRUCTION', 'PUSH_LOG', 'INF_LOG')
|
||||||
|
AND ROWNUM <= 300
|
||||||
|
ORDER BY t.NAME, c.ORDERNO
|
||||||
|
```
|
||||||
|
|
||||||
|
对所有 `OBTAINMANNER='select'` 且具有限定值组的字段,继续查 `AD_LIMITVALUE`。`AD_LIMITVALUE` 的显示列在不同版本可能叫 `DESCRIPTION`、`NAME` 或其他名称,必须先查实际结构。
|
||||||
|
|
||||||
|
### 4. 查询关系、约束和索引
|
||||||
|
|
||||||
|
- 用 `AD_REFBYTABLE` 查 BOS 主子表和接口方明细关系。
|
||||||
|
- 用 `ALL_CONSTRAINTS` / `ALL_CONS_COLUMNS` 查主键、外键和 CHECK 约束。
|
||||||
|
- 用 `ALL_INDEXES` / `ALL_IND_COLUMNS` 查状态、时间、指令和业务关键字索引。
|
||||||
|
- 特别检查 `H_INSTRUCTION.CODE` 是否唯一,以及日志的 `H_INSTRUCTION_ID` 是否可能成为孤儿。
|
||||||
|
|
||||||
|
### 5. 查处理过程和任务源码
|
||||||
|
|
||||||
|
从以下入口继续追踪:
|
||||||
|
|
||||||
|
- `AD_TABLE.TRIG_AC`、`TRIG_AM`:指令配置新增/修改后的校验过程。
|
||||||
|
- `H_INSTRUCTION.SETPCDE`:入站业务处理过程。
|
||||||
|
- `H_INSTRUCTION.PCDE_PUSH`:出站数据生成或推送准备过程。
|
||||||
|
- 依赖 `INF_LOG` 的函数/过程:查实际入站入口和状态更新逻辑。
|
||||||
|
- 依赖 `PUSH_LOG` 的过程:查出站任务、重试、状态迁移和回执处理。
|
||||||
|
- `USER_JOBS`、`USER_SCHEDULER_JOBS` 或 BOS 任务表:查调度和日志清理任务。
|
||||||
|
|
||||||
|
优先使用:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/oracle_skill.py deps <schema> <object>
|
||||||
|
python scripts/oracle_skill.py source <schema> <object>
|
||||||
|
python scripts/oracle_skill.py analyze <schema> <object>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 做有边界的数据统计
|
||||||
|
|
||||||
|
生产环境先限定最近 7 天、30 天或用户指定区间,再按指令和状态汇总。不要直接读取全部 CLOB 请求内容,也不要把敏感请求数据原样写入报告。
|
||||||
|
|
||||||
|
常见统计维度:
|
||||||
|
|
||||||
|
- 每条指令的调用量、成功量、失败量和未处理量。
|
||||||
|
- 最早/最近调用时间。
|
||||||
|
- 每日趋势、失败率和积压时长。
|
||||||
|
- 失败消息分类,区分业务拒绝和系统异常。
|
||||||
|
- 出站同步状态、重试次数和最终失败记录。
|
||||||
|
- 来源地址分布。
|
||||||
|
- 超出 `DELQTY` 或约定保留期的日志数量。
|
||||||
|
- 表段、LOB 段和索引空间。
|
||||||
|
|
||||||
|
所有业务表查询必须带日期、状态、指令或主键范围,并限制明细行数。统计 CLOB 时优先统计长度、空值和分类;只有排障确实需要时才抽取少量脱敏片段。
|
||||||
|
|
||||||
|
## 通用风险检查表
|
||||||
|
|
||||||
|
### 正确性
|
||||||
|
|
||||||
|
- 处理过程执行 `UPDATE` 后是否检查 `SQL%ROWCOUNT`,避免实际未更新却返回成功。
|
||||||
|
- 是否校验重复请求和幂等性,重试是否可能重复写业务数据。
|
||||||
|
- `CODE` 重复、大小写不一致或未知指令时是否返回明确错误。
|
||||||
|
- 动态调用的过程名是否经过白名单或严格配置权限控制。
|
||||||
|
|
||||||
|
### 可观测性
|
||||||
|
|
||||||
|
- 未处理、处理中状态是否可能长期积压。
|
||||||
|
- 技术异常是否被统一包装成普通业务失败。
|
||||||
|
- 日志是否同时保留可对外展示的信息和内部真实错误码。
|
||||||
|
- `MSG_ERROR` 是否只保存最后一次结果,导致历史被覆盖。
|
||||||
|
|
||||||
|
### 数据治理
|
||||||
|
|
||||||
|
- `DELQTY` 是否被实际任务读取,保留期是否真正执行。
|
||||||
|
- 清理前是否满足审计、归档、备份和可恢复要求。
|
||||||
|
- `JSON`、`JSON_BEFORE` 是否重复存储,原始报文是否真的可追溯。
|
||||||
|
- `DOCNO` 等搜索字段是否填充;敏感标识是否应脱敏或哈希。
|
||||||
|
- 大量 CLOB、失效索引或长期历史是否造成空间增长。
|
||||||
|
|
||||||
|
### 安全
|
||||||
|
|
||||||
|
- 是否启用来源 IP 校验,或网络层是否有等效限制。
|
||||||
|
- 请求和响应是否包含密码、令牌、手机号、证件号等敏感数据。
|
||||||
|
- 接口 URL、请求头和凭证是否被安全保存。
|
||||||
|
- 谁可以修改 `H_INSTRUCTION.SETPCDE`、`PCDE_PUSH` 和接口地址。
|
||||||
|
|
||||||
|
## 报告输出要求
|
||||||
|
|
||||||
|
通用接口分析报告至少包含:
|
||||||
|
|
||||||
|
1. 目标 client、实际 schema、分析时间和统计区间。
|
||||||
|
2. 四表是否存在、实际字段差异、行数和当前用途。
|
||||||
|
3. 指令配置清单:CODE、名称、方向、处理过程、启停、调度和重试。
|
||||||
|
4. 入站和出站数据流,以及四表关系。
|
||||||
|
5. 状态值的目标环境实查结果,不直接照搬常见值。
|
||||||
|
6. 近期调用量、成功、业务失败、技术失败、积压和来源分布。
|
||||||
|
7. 处理过程、异常处理、幂等性、重试和回执逻辑。
|
||||||
|
8. 索引、约束、日志保留、LOB 空间和敏感数据风险。
|
||||||
|
9. 明确区分:通用结构结论、目标环境事实、基于源码的推断、尚待验证事项。
|
||||||
|
|
||||||
|
报告中不得写入其他客户的接口名称、过程名、IP、账号、数据量或故障结论。发现字段或流程与本文不同时,以目标环境的实际元数据和源码为准,并把差异记录在报告中。
|
||||||
+150
-1
@@ -20,6 +20,11 @@ import io
|
|||||||
import re
|
import re
|
||||||
import html
|
import html
|
||||||
import requests
|
import requests
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import platform
|
||||||
|
import uuid
|
||||||
|
import secrets
|
||||||
from typing import Optional, Dict, Any, List, Tuple
|
from typing import Optional, Dict, Any, List, Tuple
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -1996,6 +2001,103 @@ def cmd_login(secret_key: str, client_code: str = ""):
|
|||||||
print(f" 可切换 client: {', '.join(c['code'] for c in client_list)}")
|
print(f" 可切换 client: {', '.join(c['code'] for c in client_list)}")
|
||||||
|
|
||||||
|
|
||||||
|
def _device_key_path() -> str:
|
||||||
|
return os.path.join(get_script_dir(), "device-key.json")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_device_key() -> Dict[str, Any]:
|
||||||
|
with open(_device_key_path(), "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def _save_device_key(item: Dict[str, Any]) -> None:
|
||||||
|
path = _device_key_path()
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(item, f, ensure_ascii=False, indent=2)
|
||||||
|
try:
|
||||||
|
os.chmod(path, 0o600)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _device_identity() -> Tuple[str, str]:
|
||||||
|
mac = ":".join(f"{(uuid.getnode() >> shift) & 0xff:02X}" for shift in range(40, -1, -8))
|
||||||
|
return hashlib.sha256(mac.encode("ascii")).hexdigest(), f"{mac[:8]}****{mac[-5:]}"
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_device_register(device_name: str = ""):
|
||||||
|
"""Generate an Ed25519 key locally and register this terminal as PENDING."""
|
||||||
|
try:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||||
|
except ImportError:
|
||||||
|
print("❌ 需要安装 cryptography: python -m pip install cryptography")
|
||||||
|
return
|
||||||
|
cfg = get_config() or {}
|
||||||
|
if not ensure_logged_in(cfg):
|
||||||
|
print("❌ 当前登录态已失效,请先执行 login")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
key = _load_device_key()
|
||||||
|
private = Ed25519PrivateKey.from_private_bytes(base64.b64decode(key["private_key"]))
|
||||||
|
except (OSError, KeyError, ValueError, TypeError):
|
||||||
|
private = Ed25519PrivateKey.generate()
|
||||||
|
public = private.public_key().public_bytes_raw()
|
||||||
|
key = {"device_id": "device-" + secrets.token_hex(12), "private_key": base64.b64encode(private.private_bytes_raw()).decode("ascii"), "public_key": base64.b64encode(public).decode("ascii"), "key_fingerprint": hashlib.sha256(public).hexdigest()}
|
||||||
|
_save_device_key(key)
|
||||||
|
mac_hash, mac_masked = _device_identity()
|
||||||
|
body = {"device_id": key["device_id"], "device_name": device_name or platform.node() or "Trusted device", "computer_name": platform.node(), "os_name": platform.platform(), "key_algorithm": "ED25519", "public_key": key["public_key"], "key_fingerprint": key["key_fingerprint"], "mac_hash": mac_hash, "mac_masked": mac_masked, "description": "Registered by oracle-jump-query skill"}
|
||||||
|
try:
|
||||||
|
resp = requests.post(f"{cfg.get('transit_url', TRANSIT_URL)}/api/device/register", json=body, headers=make_headers(), timeout=20)
|
||||||
|
data = resp.json()
|
||||||
|
except (requests.RequestException, ValueError) as exc:
|
||||||
|
print(f"❌ 可信设备注册失败: {exc}")
|
||||||
|
return
|
||||||
|
if not data.get("success"):
|
||||||
|
print(f"❌ 可信设备注册失败: {data.get('error', resp.text)}")
|
||||||
|
return
|
||||||
|
print("✅ 可信设备注册请求已提交,当前状态为 PENDING")
|
||||||
|
print(f" device_id: {key['device_id']}")
|
||||||
|
print(f" fingerprint: {key['key_fingerprint']}")
|
||||||
|
print(" 请在后台表单审批为 APPROVED 后执行 device_login")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_device_login(client_code: str = ""):
|
||||||
|
"""Use the locally stored Ed25519 key to perform trusted-device login."""
|
||||||
|
try:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||||
|
key = _load_device_key()
|
||||||
|
private = Ed25519PrivateKey.from_private_bytes(base64.b64decode(key["private_key"]))
|
||||||
|
except (ImportError, OSError, KeyError, ValueError, TypeError) as exc:
|
||||||
|
print(f"❌ 本机可信设备密钥不可用: {exc}")
|
||||||
|
return
|
||||||
|
transit_url = (get_config() or {}).get("transit_url", TRANSIT_URL)
|
||||||
|
identity = {"device_id": key["device_id"], "key_fingerprint": key["key_fingerprint"]}
|
||||||
|
try:
|
||||||
|
challenge_resp = requests.post(f"{transit_url}/api/device/challenge", json=identity, timeout=20)
|
||||||
|
challenge_data = challenge_resp.json()
|
||||||
|
if not challenge_data.get("success"):
|
||||||
|
print(f"❌ 获取可信设备挑战失败: {challenge_data.get('error', challenge_resp.text)}")
|
||||||
|
return
|
||||||
|
challenge = challenge_data["challenge"]
|
||||||
|
signature = base64.b64encode(private.sign(challenge.encode("utf-8"))).decode("ascii")
|
||||||
|
resp = requests.post(f"{transit_url}/api/device/login", json={**identity, "challenge": challenge, "signature": signature, "client_code": client_code}, timeout=20)
|
||||||
|
data = resp.json()
|
||||||
|
except (requests.RequestException, ValueError) as exc:
|
||||||
|
print(f"❌ 可信设备登录失败: {exc}")
|
||||||
|
return
|
||||||
|
if not data.get("success"):
|
||||||
|
print(f"❌ 可信设备登录失败: {data.get('error', resp.text)}")
|
||||||
|
return
|
||||||
|
cfg = get_config() or {}
|
||||||
|
cfg.update({"access_token": data.get("access_token", ""), "expires_at": data.get("expires_at", ""), "user_name": data.get("user", {}).get("name", ""), "client_code": data.get("current_client", {}).get("code", client_code), "client_title": data.get("current_client", {}).get("title", "")})
|
||||||
|
cfg["server_id"] = cfg.get("client_code", "")
|
||||||
|
cfg["client_list"] = data.get("client_list", [])
|
||||||
|
save_config(cfg)
|
||||||
|
print("✅ 可信设备登录成功")
|
||||||
|
print(f" 用户: {cfg.get('user_name', '')}")
|
||||||
|
print(f" 指向 client: {cfg.get('client_code', '')}")
|
||||||
|
|
||||||
|
|
||||||
def cmd_logout():
|
def cmd_logout():
|
||||||
"""登出,清除登录信息"""
|
"""登出,清除登录信息"""
|
||||||
cfg = get_config() or {}
|
cfg = get_config() or {}
|
||||||
@@ -2144,8 +2246,37 @@ def is_token_expired(cfg: dict) -> bool:
|
|||||||
return now >= expires_at
|
return now >= expires_at
|
||||||
|
|
||||||
|
|
||||||
|
def _auto_device_login(cfg: Dict[str, Any]) -> bool:
|
||||||
|
"""Refresh the transit session with the locally approved trusted device."""
|
||||||
|
try:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||||
|
key = _load_device_key()
|
||||||
|
private = Ed25519PrivateKey.from_private_bytes(base64.b64decode(key["private_key"]))
|
||||||
|
identity = {"device_id": key["device_id"], "key_fingerprint": key["key_fingerprint"]}
|
||||||
|
transit_url = cfg.get("transit_url", TRANSIT_URL)
|
||||||
|
challenge_data = requests.post(f"{transit_url}/api/device/challenge", json=identity, timeout=20).json()
|
||||||
|
if not challenge_data.get("success"):
|
||||||
|
return False
|
||||||
|
challenge = challenge_data["challenge"]
|
||||||
|
signature = base64.b64encode(private.sign(challenge.encode("utf-8"))).decode("ascii")
|
||||||
|
data = requests.post(f"{transit_url}/api/device/login", json={**identity, "challenge": challenge, "signature": signature, "client_code": cfg.get("client_code", "")}, timeout=20).json()
|
||||||
|
if not data.get("success") or not data.get("access_token"):
|
||||||
|
return False
|
||||||
|
current = data.get("current_client", {})
|
||||||
|
cfg.update({"access_token": data["access_token"], "expires_at": data.get("expires_at", ""), "user_name": data.get("user", {}).get("name", cfg.get("user_name", "")), "client_code": current.get("code", cfg.get("client_code", "")), "client_title": current.get("title", cfg.get("client_title", "")), "client_list": data.get("client_list", cfg.get("client_list", []))})
|
||||||
|
cfg["server_id"] = cfg.get("client_code", cfg.get("server_id", ""))
|
||||||
|
save_config(cfg)
|
||||||
|
print("✅ 中转 token 已通过可信设备自动续取")
|
||||||
|
return True
|
||||||
|
except (ImportError, OSError, KeyError, ValueError, TypeError, requests.RequestException):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def ensure_logged_in(cfg: Optional[dict] = None) -> bool:
|
def ensure_logged_in(cfg: Optional[dict] = None) -> bool:
|
||||||
cfg = cfg or get_config() or {}
|
cfg = cfg or get_config() or {}
|
||||||
|
if not cfg.get("access_token") or is_token_expired(cfg):
|
||||||
|
if _auto_device_login(cfg):
|
||||||
|
return True
|
||||||
if not cfg.get("access_token"):
|
if not cfg.get("access_token"):
|
||||||
print("❌ 未登录,请先执行: python oracle_skill.py login <secretKey> [clientCode]")
|
print("❌ 未登录,请先执行: python oracle_skill.py login <secretKey> [clientCode]")
|
||||||
return False
|
return False
|
||||||
@@ -3191,6 +3322,18 @@ def cmd_capabilities(args):
|
|||||||
"required": []
|
"required": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "device_register",
|
||||||
|
"description": "Generate a local Ed25519 device key and submit a trusted-device registration request.",
|
||||||
|
"parameters": {"type": "object", "properties": {"deviceName": {"type": "string", "description": "Optional device display name."}}},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "device_login",
|
||||||
|
"description": "Authenticate through an approved trusted device using a local Ed25519 private key.",
|
||||||
|
"parameters": {"type": "object", "properties": {"clientCode": {"type": "string", "description": "Optional client/server code."}}},
|
||||||
|
"required": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "awr_status",
|
"name": "awr_status",
|
||||||
"description": "Read AWR authorization, readiness and latest generated report status without generating a report.",
|
"description": "Read AWR authorization, readiness and latest generated report status without generating a report.",
|
||||||
@@ -3271,7 +3414,7 @@ def cmd_capabilities(args):
|
|||||||
"analyze", "list", "source", "describe",
|
"analyze", "list", "source", "describe",
|
||||||
"search", "query", "discover", "nl2sql",
|
"search", "query", "discover", "nl2sql",
|
||||||
"perm", "qperm", "login", "logout",
|
"perm", "qperm", "login", "logout",
|
||||||
"status", "switch", "clients", "ops", "ops_report", "inspection_report", "inspection_latest", "inspection_get", "awr_status", "awr_list", "awr_download", "agent_update", "capabilities"
|
"status", "switch", "clients", "device_register", "device_login", "ops", "ops_report", "inspection_report", "inspection_latest", "inspection_get", "awr_status", "awr_list", "awr_download", "agent_update", "capabilities"
|
||||||
],
|
],
|
||||||
"python_version": "3.6+",
|
"python_version": "3.6+",
|
||||||
"dependencies": ["requests"]
|
"dependencies": ["requests"]
|
||||||
@@ -3411,6 +3554,12 @@ def main():
|
|||||||
elif cmd == "logout":
|
elif cmd == "logout":
|
||||||
cmd_logout()
|
cmd_logout()
|
||||||
|
|
||||||
|
elif cmd == "device_register":
|
||||||
|
cmd_device_register(" ".join(sys.argv[2:]) if len(sys.argv) >= 3 else "")
|
||||||
|
|
||||||
|
elif cmd == "device_login":
|
||||||
|
cmd_device_login(sys.argv[2] if len(sys.argv) >= 3 else "")
|
||||||
|
|
||||||
elif cmd == "status":
|
elif cmd == "status":
|
||||||
cmd_status()
|
cmd_status()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user