chore: refresh public mirror

This commit is contained in:
chen qiang
2026-08-17 11:28:26 +08:00
parent 01c716adf5
commit 1738c4f849
6 changed files with 581 additions and 2 deletions
+13
View File
@@ -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.
+5
View File
@@ -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`。
- 款号和条码是一对多关系。 - 款号和条码是一对多关系。
+1 -1
View File
@@ -1 +1 @@
1.5.37 1.5.39
+412
View File
@@ -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
View File
@@ -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"]
@@ -3410,6 +3553,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()