e10-data 0.1.20 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +111 -0
- package/README.md +88 -0
- package/bin/e10-data.mjs +470 -71
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,116 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.1.23(2026-10-13)—— 新增 `refusal get`:拒答话术
|
|
4
|
+
|
|
5
|
+
### 1. 新命令族 `refusal`(子命令 `get`)
|
|
6
|
+
|
|
7
|
+
- 端点:`/api/dw/newetl/chatBi/getRefusalSpeechTechnique`,**入参仅 1 个 `type`**(场景/类型标识,由后端定义,CLI 原样透传、不做白名单校验);
|
|
8
|
+
返回对象的 `data` = **需要使用的字符串内容**(话术正文,允许为空串)。
|
|
9
|
+
- 调用方式仿照 `ds fields-dsl`(`/api/dw/newetl/chatBi/ds/fieldsForDSL`)的 `postMultipart` 表单口径,
|
|
10
|
+
默认通道策略为 **`auto`:先 POST 表单 → 返回不含 `data` 时自动回退 `GET ?type=`**(见 §3 实测确认 POST 表单即命中),
|
|
11
|
+
任一条通道拿到 `data` 即止;可用 `--http post|get` 强制单一通道。
|
|
12
|
+
- 输出:人读直接打印 `data` 话术正文(空串 → `(空:type=<type> 无话术内容)`,**空串 ≠ 失败**);
|
|
13
|
+
`--json` 原样输出整个响应对象;`E10_DATA_DEBUG=1` 向 stderr 打印实际生效通道(stdout 保持纯净)。
|
|
14
|
+
- 失败语义:两条通道均未拿到 `data` → 抛错并列出各通道 `code/msg`;**凭据失效(401)不换通道重试**,直接报登录态失效。
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
e10-data refusal get --type 1 # 人读:直接打印话术正文
|
|
18
|
+
e10-data refusal get --type 1 --json # 原样输出 {data, actionMsg}
|
|
19
|
+
e10-data refusal get --type 1 --http get # 锁定 GET 单通道
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. 同步改动
|
|
23
|
+
|
|
24
|
+
- `CMD_SUBS` 白名单新增 `refusal: ["get"]`;`main()` 分发表新增 `refusal` 分支;`usage()` 新增 `refusal` 帮助段;
|
|
25
|
+
头部注释、`README.md`(命令总览 / 用法示例 / 新增 `refusal get` 章节)同步。
|
|
26
|
+
|
|
27
|
+
### 3. 实测确认(2026-10-13)
|
|
28
|
+
|
|
29
|
+
- `type=Extraterritorial`(泛微网络租户)实测:**POST multipart 表单字段 `type` 即命中**,`auto` 无需回退 GET;
|
|
30
|
+
返回体 `{"data":"<话术字符串>","actionMsg":{"code":0,"message":"成功"}}`。契约已回填代码注释与 README。
|
|
31
|
+
|
|
32
|
+
## 0.1.22(2026-10-10)—— 移除 `eb perm-page`;逗号列表支持 `@文件`
|
|
33
|
+
|
|
34
|
+
### 1. 移除 `eb` 命令族(`eb perm-page`)—— EB 建模表单不再做权限前置校验
|
|
35
|
+
|
|
36
|
+
> ⚠️ **破坏性**:`e10-data eb perm-page` 命令**已删除**;`e10-data eb perm-page …` 现按「未知命令」处理(打印用法、退出码 1)。
|
|
37
|
+
|
|
38
|
+
- 背景:EB 建模表单取数**不再做权限判定**(数据源通道本身不拦截 EB 表权限);`--app-id` 直接取 `ebdf_obj.app_id`
|
|
39
|
+
(`obj_id` = `ebdf_obj.id` = 数据源 tableId;两值均为 19 位大整数,按字符串原样传参)。
|
|
40
|
+
- 移除范围:`CMD_SUBS` 白名单 `eb` 项、`main()` 分发表 `eb` 分支、`cmdEbPermPage()` 实现与
|
|
41
|
+
`EB_PERM_PAGE_PATH` 常量、`usage()` 的 `eb` 帮助段;并同步改写 `ds describe` / `dsRouteHint` / 头部注释等
|
|
42
|
+
指向该命令的提示为「不做权限前置校验,appId 取 `ebdf_obj.app_id`」。
|
|
43
|
+
|
|
44
|
+
### 2. `--fields` / `--group-by` / `--order` 支持 `@文件`
|
|
45
|
+
|
|
46
|
+
- 新增 `readListArg()`:逗号列表参数接受 `@文件`,文件内容可为**逗号字符串 `a,b,c`** 或 **JSON 数组 `["a","b","c"]`**,
|
|
47
|
+
与 `--where` / `--stat` / `--dsl` 一致。
|
|
48
|
+
- 目的:让逗号不出现在命令行上,规避 shell(尤其 Windows 交互式 PowerShell 把未加引号的 `a,b,c` 当数组拆开)
|
|
49
|
+
拆分导致**静默丢列 / 静默 0 行**。
|
|
50
|
+
- 覆盖:`ds list --fields`、`ds chart --group-by`、`ds list --order`、`module call --fields`;
|
|
51
|
+
`usage()` 与 `README.md` 同步标注为 `a,b|@f`。
|
|
52
|
+
|
|
53
|
+
## 0.1.21(2026-09-28)—— 新增 `module call`:模块配置接口调用(配置中心驱动)
|
|
54
|
+
|
|
55
|
+
### 1. 新命令族 `module`(子命令 `call`)
|
|
56
|
+
|
|
57
|
+
**用途**:**已确定「哪个模块的哪条具体接口」之后**,用本命令一条命令完成取数 ——
|
|
58
|
+
定位模块 → 拉接口清单 → 选接口 → **组参调用** 的前三步仍由技能侧完成,第四步「调用」收敛到本命令。
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
e10-data module call --conf-key <confKey> --group <模块> \
|
|
62
|
+
[--args '<JSON>'|--args @params.json] [--query "k=v&k2=v2"] \
|
|
63
|
+
[--method GET|POST|FORM] [--header 'K: V'|--headers '<JSON>'|@file] \
|
|
64
|
+
[--limit 50] [--fields a,b] [--raw] [--show-params] [--dry-run] [--timeout 60] [--json]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
内部一次完成:**精取该条 `confAttrs`**(`confType=api`,按 `confGroup` 二级定位)
|
|
68
|
+
→ 按 `http_method` / `input_params` 组参(GET 拼 query;POST 发 JSON body,`FORM` /
|
|
69
|
+
`api_definition` 含 `x-www-form-urlencoded` 时发表单)
|
|
70
|
+
→ 发请求(**自动禁代理** + `Cookie` / `eteamsid` / `User-Agent: AgentType=…,IsAgent=true` 三件套)
|
|
71
|
+
→ 按外层结构(`datajson` / `data` / `records` / 裸数组)提取精简结果(`rows` / `total` / `data`)。
|
|
72
|
+
|
|
73
|
+
### 2. 与技能自带 `script/call_module.py` 的等价关系
|
|
74
|
+
|
|
75
|
+
参数名、退出码、提取口径**全对齐**,可互为替换(`--args` 语义一致、`--query` 多对改用 `&` 连接、
|
|
76
|
+
`--header` 单条 + `--headers` JSON 多条)。差异只有两点:
|
|
77
|
+
|
|
78
|
+
1. **复用本 CLI 的凭据缓存与 `conf` 通道** —— 不额外起 python、不重复取凭据(少 1~2 次进程);
|
|
79
|
+
2. **零临时文件** —— body 由内存直发,不落盘。
|
|
80
|
+
|
|
81
|
+
### 3. 退出码(照 `call_module.py` 语义,`main().catch` 按 `e.exitCode` 退出)
|
|
82
|
+
|
|
83
|
+
| 码 | 含义 |
|
|
84
|
+
|---|---|
|
|
85
|
+
| 0 | 正常(**空结果也算正常**:0 行 / 计数 0 直接交付) |
|
|
86
|
+
| 2 | 未登录 / 凭据失效(HTTP 401 / 凭据取不到)→ 重登 |
|
|
87
|
+
| 3 | 非 JSON / `confAttrs` 解析失败 / 配置不存在 |
|
|
88
|
+
| 4 | 权限受限(`hasSearchRight=false`;终端态:立即终止,不重试、不换参、不交付为「无数据」) |
|
|
89
|
+
| 6 | 命中 HRM 路径黑名单(`/api/hrm/`、`/api/bs/hrm/`、`/api/app/hrm/` → 拒绝调用) |
|
|
90
|
+
| 7 | 请求失败(网络 / 超时 / HTTP 非 2xx 且非 JSON) |
|
|
91
|
+
| 8 | 用法错误(缺必填 / 参数非法 / `--args` 非 JSON 对象) |
|
|
92
|
+
|
|
93
|
+
### 4. 功能要点
|
|
94
|
+
|
|
95
|
+
- `--show-params`:只打印该接口的入/出参说明与请求示例(**不调接口**),替代手工 `conf get` dump 用于正确组参;
|
|
96
|
+
- `--dry-run`:只组装请求不发送,**凭据一律脱敏**;
|
|
97
|
+
- `--limit N`(默认 50,`0` = 不截断)/ `--fields a,b`(每行投影)/ `--raw`(原样输出响应体);
|
|
98
|
+
- **权限/二次验证标志位识别**:`hasSearchRight=false` 与 `pageUid="checkSecondFail"` 在输出 `flags` 中点名
|
|
99
|
+
(前者同时以退出码 4 收口);
|
|
100
|
+
- **INVOICE 自动换取发票云 `token`**:`group` / `module_key` 为 `INVOICE` 时先 `GET /api/inc/proxy/getToken?appType=1`
|
|
101
|
+
取 `data.token` 写入请求头,**Token 只在内存透传,不打印、不落盘**;
|
|
102
|
+
- **HRM 路径黑名单**在发请求前拒绝(退出码 6),杜绝手工直调 HRM 接口。
|
|
103
|
+
|
|
104
|
+
### 5. 配套改动(非破坏性)
|
|
105
|
+
|
|
106
|
+
- `normalizeAuth` / weaver-work-cli 出口 / 进程间凭据缓存**补齐 `userAgent`** 字段
|
|
107
|
+
(来源 `WEAVER_USER_AGENT`),供 `module call` 组 `AgentType` UA;缺失或形态不符时
|
|
108
|
+
由 `moduleUserAgent()` 从 `cookie` 的 `agentType=` 推导,再退回 `workbuddy`。**取数链路行为不变**;
|
|
109
|
+
- `main().catch` 支持错误对象自带 `exitCode`(未设置仍为 1,既有命令退出码零变化)。
|
|
110
|
+
|
|
111
|
+
> 📌 命令参数、输出与退出码是本包**对外契约**;技能侧 `references/support/module-api-query/module-api-query.md`
|
|
112
|
+
> 的「组参调用」步骤可切到本命令(`call_module.py` 保留为兜底)。
|
|
113
|
+
|
|
3
114
|
## 0.1.20(2026-09-24)—— 凭据来源切换为 `weaver-work-cli`(唯一来源,移除 e10-login 回退)
|
|
4
115
|
|
|
5
116
|
### 1. 凭据出口改为 `weaver-work-cli auth export-weaver-env`
|
package/README.md
CHANGED
|
@@ -25,12 +25,15 @@ e10-data field id2name|name2id 关联字段翻译(test
|
|
|
25
25
|
e10-data conf get|batch|batch-file|group|group-file|resolve|resolve-file|add|add-file|update|delete 环境参数配置中心(chatBi config map,替代 config-resolve 脚本 URL 直连;confType 默认按 CONN_/DS_/WF_/API_ 前缀推导,可 --type 显式;confType=api 时支持 confGroup 分组,见下)
|
|
26
26
|
e10-data exp status|manifest|commits|file|pull|init|commit|lock|release|rollback 专家包版本管理
|
|
27
27
|
e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-attendance 病假/事假等,v0.1.5 新增)
|
|
28
|
+
e10-data module call 模块配置接口调用(配置中心驱动;已确定「哪个模块的哪条具体接口」之后的统一取数入口,v0.1.21 新增)
|
|
29
|
+
e10-data refusal get --type <type> 拒答话术 getRefusalSpeechTechnique(入参 type → data 话术字符串,v0.1.23 新增)
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
## 通用约定
|
|
31
33
|
|
|
32
34
|
- `--json`:原样输出接口 JSON(供脚本/Agent 消费);默认人读输出。
|
|
33
35
|
- 复杂 JSON/中文参数:推荐 `@文件` 承载(如 `--dsl @q.json`、`--where @cond.json`)。
|
|
36
|
+
- **逗号列表参数**(`--fields` / `--group-by` / `--order`):**一律走 `@文件`** —— 逗号不出现在命令行上,避免被 shell(尤其 Windows 交互式 PowerShell 把未加引号的 `a,b,c` 当数组拆开)拆分后**静默丢列 / 静默 0 行**。文件内容两种形态均可:逗号字符串 `a,b,c` 或 JSON 数组 `["a","b","c"]`。
|
|
34
37
|
- ⚠️ 大整数精度(自动处理):E10 的 ID 多为 19 位,超过 JS 安全整数(2^53-1≈9e15)。CLI 解析 JSON 时**自动把超范围裸整数转成字符串**再发请求/输出(实测后端接受字符串做 =/IN/BETWEEN 数值比较),无需手动加引号,也不误伤字符串正文。
|
|
35
38
|
- 认证:**加解密由 `e10-login` 负责(本 CLI 不参与)**。凭据出口:`weaver-work-cli auth export-weaver-env`(解析 `WEAVER_BASE_URL` / `WEAVER_ETEAMSID` / `WEAVER_COOKIE`,另补一次 `auth status` 取 `userId` / `tenantKey`),**已移除 e10-login 回退**。命中的出口会记入 `~/.e10-cli/.e10-data-auth-mode`(只存模式名、不存凭据),下次优先复用。环境变量:`E10_DATA_AUTH_PROBE=1` 强制重新探测、`E10_DATA_AUTH_MODE=<模式名>` 手动指定、`E10_DATA_QUIET=1` 抑制告警(告警只走 stderr)。**没有任何可用凭据时**用 `e10-data login xiaoe-env` 登录(见下节)。
|
|
36
39
|
- 版本更新:`npm i -g e10-data@latest`;查看源上最新 `npm view e10-data version`。
|
|
@@ -88,6 +91,9 @@ e10-data exp status --expert data-assistant
|
|
|
88
91
|
e10-data exp pull --expert data-assistant --dir <本地专家包目录> [--dry-run] [--force]
|
|
89
92
|
e10-data attend statis # 查当前登录用户当月假期余额(病假/事假/年假)
|
|
90
93
|
e10-data attend statis --user 6264843388578732287 --begin 2026-09-01 --end 2026-09-30
|
|
94
|
+
e10-data refusal get --type <type> # 拒答话术(人读:直接打印话术正文)
|
|
95
|
+
e10-data refusal get --type <type> --json # 原样输出整个响应对象(data + actionMsg)
|
|
96
|
+
e10-data refusal get --type <type> --http get # 强制走 GET ?type=(默认 auto:POST 表单优先、失败回退 GET)
|
|
91
97
|
# 无凭据时先登录(环境变量里的 XiaoE 全局 token 换 ETEAMSID)
|
|
92
98
|
XIAOE_USER_TOKEN=<XiaoE token> XIAOE_DOMAIN=<erpa域名> e10-data login xiaoe-env --domain https://www.e-cology.com.cn
|
|
93
99
|
```
|
|
@@ -264,3 +270,85 @@ e10-data attend statis [--user <人员ID>] [--begin 2026-09-01] [--end 2026-09-3
|
|
|
264
270
|
- `--json`:原样输出接口 JSON(含 `data.attendStatis` 出勤统计 / `data.attendSummary` 汇总 / `data.leaveInfo` 请假 / `data.outAttend` 外出 / `data.vacationBalanceList` 假期余额);
|
|
265
271
|
- 默认人读输出:仅列出 `data.vacationBalanceList`(按 title 区分假期类型)——带薪病假 `100584850000000003`、带薪事假 `100584850000000010` 含 `lastRemaining`(历年可请)/`remaining`(今年可请)/`total`(总计可请) 三项。
|
|
266
272
|
|
|
273
|
+
## module call 模块配置接口调用(v0.1.21 新增)
|
|
274
|
+
|
|
275
|
+
**用途**:**已确定「哪个模块的哪条具体接口」之后**的统一取数入口。定位模块 → 拉接口清单 → 选接口仍由技能侧完成,
|
|
276
|
+
**第四步「组参调用」收敛到本命令**:精取该条 `confAttrs` → 按 `http_method` / `input_params` 组参
|
|
277
|
+
→ 发请求(自动禁代理 + `Cookie` / `eteamsid` / `User-Agent: AgentType=…,IsAgent=true` 三件套)
|
|
278
|
+
→ 按外层结构(`datajson` / `data` / `records` / 裸数组)提取精简结果。
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
e10-data module call --conf-key <confKey> --group <模块> \
|
|
282
|
+
[--args '<JSON>'|--args @params.json] [--query "k=v&k2=v2"] \
|
|
283
|
+
[--method GET|POST|FORM] [--header 'K: V'|--headers '<JSON>'|@file] \
|
|
284
|
+
[--limit 50] [--fields a,b|@f] [--raw] [--show-params] [--dry-run] [--timeout 60] [--json]
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
| 参数 | 说明 |
|
|
288
|
+
|---|---|
|
|
289
|
+
| `--conf-key` / `--group` | **必填**;`confKey` 取自模块接口清单,`--group` = 模块唯一标识(confGroup,如 `WORKFLOW` / `EHR` / `DOCUMENT`) |
|
|
290
|
+
| `--args` | 业务参数 JSON 对象;**含中文一律用 `--args @文件`**(规避 shell 编码损坏) |
|
|
291
|
+
| `--query` | 追加 query(`k=v&k2=v2`,多对用 `&` 连接;与 `--args` 合并) |
|
|
292
|
+
| `--method` | 覆盖 `confAttrs.http_method`(`FORM` = `x-www-form-urlencoded`) |
|
|
293
|
+
| `--header` / `--headers` | 追加请求头:单条 `'K: V'`,或 JSON 对象(可 `@文件`) |
|
|
294
|
+
| `--show-params` | **只打印该接口的入/出参说明与请求示例、不调接口** —— 替代手工 `conf get` dump,用于正确组参 |
|
|
295
|
+
| `--dry-run` | 只组装请求不发送(**凭据一律脱敏**) |
|
|
296
|
+
| `--limit` / `--fields` | 列表行数上限(默认 50,`0` = 不截断)/ 每行字段投影 |
|
|
297
|
+
| `--raw` | 不提取,原样输出响应体 |
|
|
298
|
+
|
|
299
|
+
- **与技能自带 `script/call_module.py` 等价**(参数名 / 退出码 / 提取口径全对齐,可互为替换),差异只有两点:
|
|
300
|
+
① **复用本 CLI 的凭据缓存与 `conf` 通道**(不额外起 python、不重复取凭据);② **零临时文件**(body 内存直发,不落盘)。
|
|
301
|
+
- **退出码**(`main().catch` 按 `e.exitCode` 退出):`0` 正常(**空结果也算正常**)|`2` 未登录 / 凭据失效 → 重登
|
|
302
|
+
|`3` 非 JSON / `confAttrs` 异常 / 配置不存在 |`4` 权限受限(`hasSearchRight=false`,终端态:立即终止)
|
|
303
|
+
|`6` 命中 HRM 路径黑名单(拒绝调用)|`7` 请求失败 |`8` 用法错误。
|
|
304
|
+
- **标志位点名**:`hasSearchRight=false`(权限受限 ≠ 无数据)与 `pageUid="checkSecondFail"`(二次身份验证:
|
|
305
|
+
`total` 可信、明细恒 0 行)会在输出 `flags` 中列出,前者同时以退出码 `4` 收口。
|
|
306
|
+
- **INVOICE 自动换取发票云 `token`**:`--group` / `module_key` 为 `INVOICE` 时先 `GET /api/inc/proxy/getToken?appType=1`
|
|
307
|
+
取 `data.token` 写入请求头;**Token 只在内存透传,不打印、不落盘**。
|
|
308
|
+
- **HRM 路径黑名单**:`/api/hrm/`、`/api/bs/hrm/`、`/api/app/hrm/` 的接口在发请求前直接拒绝(退出码 `6`)——
|
|
309
|
+
人员 / 部门 / 分部 / 岗位一律走 `weaver-work-cli` 的 `hrm` 子命令。
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
# 看某个接口要什么参数(不调接口)
|
|
313
|
+
e10-data module call --conf-key POST_EHR_GET_REPORT_LIST --group EHR --show-params
|
|
314
|
+
|
|
315
|
+
# 真实取数(含中文参数走 @文件)
|
|
316
|
+
e10-data module call --conf-key POST_EHR_GET_REPORT_LIST --group EHR --args @params.json --json
|
|
317
|
+
|
|
318
|
+
# GET 接口 + 只看两行、只留三个字段
|
|
319
|
+
e10-data module call --conf-key GET_XXX --group XXX --query "pageNo=1&pageSize=20" --limit 2 --fields id,name --json
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## refusal get 拒答话术(v0.1.23 新增)
|
|
323
|
+
|
|
324
|
+
**用途**:查询**拒答话术**(命中拒答场景时对用户说的话术文案)。接口 `/api/dw/newetl/chatBi/getRefusalSpeechTechnique`,
|
|
325
|
+
入参只有 1 个 `type`,返回对象中的 `data` 即**需要使用的字符串内容**。
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
e10-data refusal get --type <type> [--http auto|post|get] [--json]
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
| 参数 | 说明 |
|
|
332
|
+
|---|---|
|
|
333
|
+
| `--type` | **必填**;场景/类型标识,取值由后端定义(CLI **原样透传、不做白名单校验**) |
|
|
334
|
+
| `--http` | 通道:`auto`(默认)= 先 POST multipart 表单 → 返回不含 `data` 时**自动回退** `GET ?type=`,任一条拿到 `data` 即止;`post` = 仅 POST 表单(与 `ds fields-dsl` 同口径);`get` = 仅 `GET ?type=` |
|
|
335
|
+
|
|
336
|
+
- **已实测的接口契约**(2026-10-13,泛微网络租户,`type=Extraterritorial`):
|
|
337
|
+
请求 = `POST /api/dw/newetl/chatBi/getRefusalSpeechTechnique`,**multipart 表单字段 `type`**;
|
|
338
|
+
返回 = `{"data":"<话术字符串>","actionMsg":{"code":0,"message":"成功"}}` ⇒ 正常路径**只发 1 次请求**(`auto` 无需回退)。
|
|
339
|
+
- **人读输出**:直接打印 `data` 话术正文;`data` 为空串时打印 `(空:type=<type> 无话术内容)`(**空串 ≠ 调用失败**)。
|
|
340
|
+
- **`--json`**:原样输出整个响应对象(`{"data":"…","actionMsg":{"code":0,"message":"成功"}}`;**成功标记看 `actionMsg.code`,无顶层 `code`/`msg`**),供脚本 / Agent 消费。
|
|
341
|
+
- **排障**:`E10_DATA_DEBUG=1` 时向 **stderr** 打印实际生效的通道(`post` / `get`),stdout 保持纯净。
|
|
342
|
+
- **两种通道均未拿到 `data`**:抛错并列出每条通道的 `code/msg`;凭据失效(401)时**不换通道重试**,直接报登录态失效。
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# 人读:直接拿到话术正文
|
|
346
|
+
e10-data refusal get --type 1
|
|
347
|
+
|
|
348
|
+
# 取结构化结果(Agent / 脚本)
|
|
349
|
+
e10-data refusal get --type 1 --json
|
|
350
|
+
|
|
351
|
+
# 该端点确认走 GET 时,锁定单通道(少一次探测请求)
|
|
352
|
+
e10-data refusal get --type 1 --http get
|
|
353
|
+
```
|
|
354
|
+
|
package/bin/e10-data.mjs
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* 数据源(业务模块)取数与元数据
|
|
12
12
|
* groups 内置分组清单(仅作 find 的默认扫描集)
|
|
13
13
|
* find 跨分组找表(进程内并发扇出;产物=候选表清单 + 路由建议,不取数)
|
|
14
|
-
* describe 一次并发取回「字段 +
|
|
14
|
+
* describe 一次并发取回「字段 + 词库」(不做权限校验)
|
|
15
15
|
* appId 一律由调用方显式传入(v0.1.14 起 CLI 不再自动反查):
|
|
16
16
|
* EB 建模表单(groupId=weaver-ebuilder-form-service)取数必须带 --app-id <id>,
|
|
17
|
-
*
|
|
17
|
+
* 该值取自 ebdf_obj.app_id(2026-10-09 起不再做 EB 权限前置校验);
|
|
18
18
|
* 不带 appId 时按原样发送,由后端报错(CLI 不做拦截)
|
|
19
19
|
* e10-data doc count|list 文档查询 documentListCount / documentList
|
|
20
20
|
* e10-data noun list 词库 getChatDocsBySkillKey
|
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
* 环境参数配置中心(chatBi config map,替代 config-resolve 脚本 URL 直连;
|
|
24
24
|
* confType=api 时支持 confGroup 分组二级定位,见 --group)
|
|
25
25
|
* e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-attendance 病假/事假,2026-09-07)
|
|
26
|
+
* e10-data module call 模块配置接口调用(配置中心驱动):**已确定「哪个模块的哪条具体接口」之后**的
|
|
27
|
+
* 统一取数入口 —— 精取该条 confAttrs → 按 http_method/input_params 组参 →
|
|
28
|
+
* 发请求(禁代理 + Cookie/eteamsid/UA 三件套)→ 提取精简结果(2026-09-28)
|
|
29
|
+
* 与技能自带 script/call_module.py 等价(参数/退出码/提取全对齐),
|
|
30
|
+
* 复用本 CLI 凭据缓存与 conf 通道、零临时文件
|
|
31
|
+
* e10-data refusal get 拒答话术 getRefusalSpeechTechnique(入参 type → data 话术字符串;
|
|
32
|
+
* auto 通道 = POST 表单优先、GET ?type= 回退,2026-10-13)
|
|
26
33
|
*
|
|
27
34
|
* 通用:--json 原样输出;复杂 JSON 参数支持 @文件 或 内联(中文走 @文件 更稳)
|
|
28
35
|
* 认证:凭据**唯一来源 = weaver-work-cli**(本 CLI 不实现任何加解密,**不设 e10-login 回退**):
|
|
@@ -200,7 +207,9 @@ function normalizeAuth(o) {
|
|
|
200
207
|
const cookie = String(o.cookie || "").trim();
|
|
201
208
|
if (!baseUrl || !eteamsid || !cookie) return null;
|
|
202
209
|
if (/^\((未知|空)\)$/.test(eteamsid) || /^\((未知|空)\)$/.test(cookie)) return null;
|
|
203
|
-
|
|
210
|
+
// userAgent:模块配置通道的请求头需要 `AgentType=<agentType>,IsAgent=true`(见 module 命令族)。
|
|
211
|
+
// 属"非凭据"的请求头取值,缺失时由 moduleUserAgent() 从 cookie 的 agentType= 推导,不影响取数。
|
|
212
|
+
return { baseUrl, eteamsid, cookie, userId: String(o.userId || "").trim(), tenantKey: String(o.tenantKey || "").trim(), userAgent: String(o.userAgent || "").trim(), source: o.source };
|
|
204
213
|
}
|
|
205
214
|
|
|
206
215
|
/** ① e10-login auth --json(正式契约:{v,baseUrl,eteamsId,cookie,userAgent,userId,tenantKey,agentType,profile}) */
|
|
@@ -392,6 +401,7 @@ function providerWeaverWorkCli() {
|
|
|
392
401
|
baseUrl: env.WEAVER_BASE_URL,
|
|
393
402
|
eteamsid: env.WEAVER_ETEAMSID,
|
|
394
403
|
cookie: env.WEAVER_COOKIE,
|
|
404
|
+
userAgent: env.WEAVER_USER_AGENT,
|
|
395
405
|
source: "weaver-work-cli auth export-weaver-env",
|
|
396
406
|
});
|
|
397
407
|
if (!auth) return null;
|
|
@@ -547,7 +557,7 @@ function credCachePublish(auth) {
|
|
|
547
557
|
profile: auth.profile || "default",
|
|
548
558
|
auth: {
|
|
549
559
|
baseUrl: auth.baseUrl, eteamsid: auth.eteamsid, cookie: auth.cookie,
|
|
550
|
-
userId: auth.userId, tenantKey: auth.tenantKey, source: auth.source,
|
|
560
|
+
userId: auth.userId, tenantKey: auth.tenantKey, userAgent: auth.userAgent, source: auth.source,
|
|
551
561
|
},
|
|
552
562
|
});
|
|
553
563
|
let settled = false;
|
|
@@ -698,7 +708,8 @@ const CMD_SUBS = {
|
|
|
698
708
|
conf: ["get", "batch", "batch-file", "group", "group-file", "resolve", "resolve-file", "add", "add-file", "update", "delete"],
|
|
699
709
|
exp: ["status", "manifest", "commits", "file", "pull", "init", "commit", "lock", "release", "rollback"],
|
|
700
710
|
attend: ["statis"],
|
|
701
|
-
|
|
711
|
+
module: ["call"],
|
|
712
|
+
refusal: ["get"],
|
|
702
713
|
};
|
|
703
714
|
function commandIsKnown(pos) {
|
|
704
715
|
const family = pos[0], sub = pos[1];
|
|
@@ -886,6 +897,21 @@ function readJsonArg(val, label) {
|
|
|
886
897
|
if (val.startsWith("@")) raw = fs.readFileSync(val.slice(1), "utf-8");
|
|
887
898
|
return parseJsonSafe(raw, label);
|
|
888
899
|
}
|
|
900
|
+
// 逗号列表参数(--fields / --group-by / --order)取值:支持 @文件,与 --where / --stat / --dsl 一致。
|
|
901
|
+
// 目的:让逗号列表不出现在命令行上,避免被 shell(尤其 Windows 交互式 PowerShell 把未加引号的 a,b,c 当数组拆开)
|
|
902
|
+
// 拆成多个独立参数 —— 拆分后不报错,会静默丢列或静默 0 行(2026-10-09 实测)。
|
|
903
|
+
// 文件内容两种形态均可:逗号字符串 "a,b,c" 或 JSON 数组 ["a","b","c"]。
|
|
904
|
+
function readListArg(val) {
|
|
905
|
+
if (val === undefined || val === null) return val;
|
|
906
|
+
let raw = String(val);
|
|
907
|
+
if (raw.startsWith("@")) raw = fs.readFileSync(raw.slice(1), "utf-8");
|
|
908
|
+
raw = raw.trim();
|
|
909
|
+
if (raw.startsWith("[")) {
|
|
910
|
+
const arr = parseJsonSafe(raw, "列表文件");
|
|
911
|
+
if (Array.isArray(arr)) return arr.map((x) => String(x)).join(",");
|
|
912
|
+
}
|
|
913
|
+
return raw;
|
|
914
|
+
}
|
|
889
915
|
function need(arg, args) { if (!args[arg]) throw new Error("缺少必填参数 --" + arg); return args[arg]; }
|
|
890
916
|
// 文件/JSON 参数取值:兼容 --flag <v> 与位置参数 <v>(如 conf add-file @items.json / --add-file @items.json)
|
|
891
917
|
function flagOrPos(args, flag, keys) {
|
|
@@ -992,8 +1018,9 @@ function parseStat(val) {
|
|
|
992
1018
|
return list.map((s) => ({ fieldId: s.fieldId, fieldName: s.fieldId, objId: "", mainField: true, aggregator: s.aggregator || "SUM", ...(s.fieldAlias ? { fieldAlias: s.fieldAlias } : {}) }));
|
|
993
1019
|
}
|
|
994
1020
|
function parseOrder(val) {
|
|
995
|
-
|
|
996
|
-
|
|
1021
|
+
const v = readListArg(val);
|
|
1022
|
+
if (!v) return [];
|
|
1023
|
+
return v.split(",").map((s) => {
|
|
997
1024
|
const [f, dir] = s.trim().split(/\s+/);
|
|
998
1025
|
return { fieldId: f, fieldName: f, objId: "", isAsc: (dir || "asc").toLowerCase() !== "desc", mainField: true };
|
|
999
1026
|
});
|
|
@@ -1060,7 +1087,7 @@ async function cmdFieldsForDSL(args, json) {
|
|
|
1060
1087
|
}
|
|
1061
1088
|
}
|
|
1062
1089
|
async function cmdList(args, json) {
|
|
1063
|
-
const gid = need("group", args), tid = need("table", args), fld = need("fields", args);
|
|
1090
|
+
const gid = need("group", args), tid = need("table", args), fld = readListArg(need("fields", args));
|
|
1064
1091
|
const fields = fld.split(",").map((f) => ({ fieldId: f, fieldName: f, objId: String(tid), mainField: true }));
|
|
1065
1092
|
const dto = await dsDto(args, tid, gid, parseInt(args.page || "1", 10), parseInt(args.size || "20", 10), "getListData");
|
|
1066
1093
|
dto.queryFields = fields;
|
|
@@ -1074,7 +1101,7 @@ async function cmdList(args, json) {
|
|
|
1074
1101
|
printHuman("", data.list || [], fields.map((f) => f.fieldId));
|
|
1075
1102
|
}
|
|
1076
1103
|
async function cmdChart(args, json) {
|
|
1077
|
-
const gid = need("group", args), tid = need("table", args), gb = need("group-by", args);
|
|
1104
|
+
const gid = need("group", args), tid = need("table", args), gb = readListArg(need("group-by", args));
|
|
1078
1105
|
const dto = await dsDto(args, tid, gid, parseInt(args.page || "1", 10), parseInt(args.size || "100", 10), "getChartData");
|
|
1079
1106
|
dto.groupFields = gb.split(",").map((f) => ({ fieldId: f, fieldName: f, objId: String(tid), mainField: true }));
|
|
1080
1107
|
dto.groupStatFields = parseStat(need("stat", args)).map((s) => ({ ...s, objId: String(tid) }));
|
|
@@ -1112,9 +1139,8 @@ async function cmdCount(args, json) {
|
|
|
1112
1139
|
// 故把「找表」「字段+词库」各收敛成 1 次 CLI 调用,**在进程内并发发 HTTP**(一次凭据搞定)。
|
|
1113
1140
|
// 实测同样的全分组扇出:进程内并发 ≈5.3~5.9s(逐进程 47.5s / 逐进程串行 ~313s)。
|
|
1114
1141
|
//
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
// ⇒ EB 建模表单取数前**必须单独**执行 `e10-data eb perm-page`,appId 只从那次调用取。
|
|
1142
|
+
// 权限闸门已取消(2026-10-09 用户要求):EB 建模表单取数不再做权限判定。
|
|
1143
|
+
// appId 直接用 ebdf_obj.app_id(obj_id = ebdf_obj.id = 数据源 tableId;均按字符串原样传参)。
|
|
1118
1144
|
|
|
1119
1145
|
// 分组清单(技能文档 data-source-query.md 1.1 的 58 个)。**仅作 ds find 的「默认扫描候选集」**,
|
|
1120
1146
|
// 不参与任何取数参数推导。分组随环境模块安装情况增减,故支持 --groups 显式覆盖。
|
|
@@ -1317,7 +1343,7 @@ function dsRouteHint(cands) {
|
|
|
1317
1343
|
return { channel: "WORKFLOW", desc: "WORKFLOW 模块配置通道 —— 流程类问题一律走模块接口,不用 DSL,也不走数据源", reason: "命中流程类分组" };
|
|
1318
1344
|
}
|
|
1319
1345
|
if (has((c) => c.groupId === "weaver-ebuilder-form-service" || c.serviceMark === "ebuilder")) {
|
|
1320
|
-
return { channel: "DS_EB", desc: "数据源通道(EB 建模表单)——
|
|
1346
|
+
return { channel: "DS_EB", desc: "数据源通道(EB 建模表单)—— 不做权限前置校验,appId 取自 ebdf_obj.app_id", reason: "命中 EB 分组" };
|
|
1321
1347
|
}
|
|
1322
1348
|
if (has((c) => c.serviceMark === "form")) {
|
|
1323
1349
|
return { channel: "DS_FORM", desc: "数据源通道(业务表单 / 流程业务表单)", reason: "命中业务表单分组" };
|
|
@@ -1424,8 +1450,7 @@ async function cmdDsFind(args, json) {
|
|
|
1424
1450
|
}
|
|
1425
1451
|
|
|
1426
1452
|
// describe:一次并发取回「字段 + 词库」(两者入参同为 (groupId, tableId),彼此无依赖)
|
|
1427
|
-
//
|
|
1428
|
-
// (appId 只从那次调用取)。原因:数据源通道本身不拦权限,权限判定与取数必须分开。
|
|
1453
|
+
// 本命令**不做权限校验**:EB 建模表单取数不再做权限前置校验(2026-10-09 取消),appId 直接取 ebdf_obj.app_id。
|
|
1429
1454
|
function collectFieldNodes(list, out, subName) {
|
|
1430
1455
|
for (const f of list || []) {
|
|
1431
1456
|
if (!f) continue;
|
|
@@ -1511,10 +1536,10 @@ async function cmdDsDescribe(args, json) {
|
|
|
1511
1536
|
}
|
|
1512
1537
|
out.relatedFields = dsRelatedHint(gid, (out.fields && out.fields.detailCount) || 0);
|
|
1513
1538
|
out.channelHint = dsRouteHint([{ groupId: gid, serviceMark: dsGroupMark(gid) }]);
|
|
1514
|
-
//
|
|
1539
|
+
// 权限闸门已取消(2026-10-09 用户要求):EB 建模表单取数不再做权限判定,appId 直接取 ebdf_obj.app_id
|
|
1515
1540
|
out.permissionNote = gid === "weaver-ebuilder-form-service"
|
|
1516
|
-
? "该分组是 EB
|
|
1517
|
-
: "非 EB 分组,数据源通道无需
|
|
1541
|
+
? "该分组是 EB 建模表单:取数不再做权限前置校验,--app-id 直接取 ebdf_obj.app_id(obj_id = ebdf_obj.id = 数据源 tableId)"
|
|
1542
|
+
: "非 EB 分组,数据源通道无需 appId";
|
|
1518
1543
|
out.elapsedMs = Date.now() - t0;
|
|
1519
1544
|
if (errors.length && args.require) throw new Error("describe 存在失败分支,--require 下不继续:" + JSON.stringify(errors));
|
|
1520
1545
|
if (json) return out;
|
|
@@ -1528,46 +1553,7 @@ async function cmdDsDescribe(args, json) {
|
|
|
1528
1553
|
else console.log("词库: " + (lx.noLexicon ? "该数据集无词库" : "命中 " + lx.count + " 条(skill=" + lx.skillKey + ")"));
|
|
1529
1554
|
console.log("关联字段: " + (out.relatedFields.likelyApplicable ? "适用(" + out.relatedFields.tableOrigin + ")" : "不适用(" + out.relatedFields.tableOrigin + ")") + " —— " + out.relatedFields.note);
|
|
1530
1555
|
console.log("通道提示: " + out.channelHint.channel + " —— " + out.channelHint.desc);
|
|
1531
|
-
console.log("权限:
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
// ---------------- eb:EB 建模表单权限校验 ----------------
|
|
1535
|
-
// chatBi EB 配置映射分页查询:EB 表权限前置校验的 CLI 通道
|
|
1536
|
-
// (替代原「DSL 查 dw.chat_bi_eb_config_map + 客户端拼 (all_scop=1 OR target_id=本人)」写法)
|
|
1537
|
-
// 服务端已按登录人过滤:有权限 → total>=1 且行内回带 appId;无权限 / 表不存在 → total=0
|
|
1538
|
-
const EB_PERM_PAGE_PATH = "/api/dw/newetl/chatBi/getChatBiEbConfigMapByPage";
|
|
1539
|
-
async function cmdEbPermPage(args, json) {
|
|
1540
|
-
// ⚠️ obj-id / app-id 为 19 位大整数:必须原样字符串传递。
|
|
1541
|
-
// 经 JS Number 会舍入(840360795890851856 → 840360795890851840),实测后端接受字符串,
|
|
1542
|
-
// 但吃下舍入后的数字会返回 total=0 —— 把「有权限」静默伪装成「无权限」。
|
|
1543
|
-
const objId = String(need("obj-id", args));
|
|
1544
|
-
const formTable = String(need("form-table", args));
|
|
1545
|
-
// ⚠️ obj-id / form-table 缺失时后端不是报错,而是 HTTP 200 + 无 actionMsg + data 空,
|
|
1546
|
-
// 外观与「无权限」完全一致 → 必须用 need() 在本地拦掉(need 对空串同样报错)。
|
|
1547
|
-
const body = {
|
|
1548
|
-
objId,
|
|
1549
|
-
formTable,
|
|
1550
|
-
pageNum: parseInt(args.page || "1", 10),
|
|
1551
|
-
pageSize: parseInt(args.size || "10", 10),
|
|
1552
|
-
};
|
|
1553
|
-
if (typeof args["app-id"] === "string" && args["app-id"] !== "") body.app_id = String(args["app-id"]);
|
|
1554
|
-
const r = await postJson(EB_PERM_PAGE_PATH, body);
|
|
1555
|
-
if (json) return r;
|
|
1556
|
-
// ⚠️ 失败判定:非法入参时 actionMsg 缺失(code=undefined),业务失败时 code!==0 → 一并按失败输出
|
|
1557
|
-
const am = r.actionMsg || {};
|
|
1558
|
-
if (am.code !== 0) { console.log("code=" + am.code + " msg=" + (am.message || "")); return; }
|
|
1559
|
-
const d = r.data || {};
|
|
1560
|
-
const recs = d.records || [];
|
|
1561
|
-
const total = d.total === undefined || d.total === null ? 0 : d.total;
|
|
1562
|
-
const first = recs[0] || null;
|
|
1563
|
-
console.log("total = " + total);
|
|
1564
|
-
printHuman("", recs, ["id", "objId", "formTable", "appId", "allScop", "targetId", "enabled", "deleteType"]);
|
|
1565
|
-
console.log(total >= 1
|
|
1566
|
-
? (first
|
|
1567
|
-
? "结论: 有权限(appId=" + first.appId + ")"
|
|
1568
|
-
: "结论: 有权限(total>=1;当前页无记录,appId 请用 --page 1 获取)")
|
|
1569
|
-
: "结论: 无权限(total=0)");
|
|
1570
|
-
if (args.require && total < 1) throw new Error("没有查询该 EB 表的权限(total=0)");
|
|
1556
|
+
console.log("权限: " + out.permissionNote);
|
|
1571
1557
|
}
|
|
1572
1558
|
|
|
1573
1559
|
// ---------------- doc:文档查询 ----------------
|
|
@@ -1993,6 +1979,338 @@ async function cmdConf(sub, args, json, pos) {
|
|
|
1993
1979
|
}
|
|
1994
1980
|
}
|
|
1995
1981
|
|
|
1982
|
+
// ---------------- module:模块配置接口调用(配置中心驱动,2026-09-28 新增)----------------
|
|
1983
|
+
// 用途:**已确定「哪个模块的哪条具体接口」之后**,用本命令一条命令完成取数:
|
|
1984
|
+
// 复用本 CLI 已加载的凭据 → 精取该条 confAttrs(confType=api,按 confGroup 二级定位)
|
|
1985
|
+
// → 按 http_method / input_params 组参 → 发请求(自动禁代理 + Cookie/eteamsid/UA 三件套)
|
|
1986
|
+
// → 按外层结构提取精简结果(datajson / data / records / 裸数组皆可)。
|
|
1987
|
+
// 与技能自带 python 调用器 call_module.py **等价**(参数名、退出码、提取口径全对齐),区别:
|
|
1988
|
+
// · 复用本 CLI 的凭据缓存与 conf 通道(不额外起 python,也不重复取凭据);
|
|
1989
|
+
// · 全程零临时文件(body 由内存直发,不落盘)。
|
|
1990
|
+
// 退出码(照 call_module.py 语义,见 main().catch 的 exitCode 处理):
|
|
1991
|
+
// 0 正常(**空结果也算正常**)/ 2 未登录·凭据失效 / 3 非 JSON·confAttrs 异常
|
|
1992
|
+
// / 4 权限受限(终端态:立即终止)/ 6 命中 HRM 路径黑名单 / 7 请求失败 / 8 用法错误
|
|
1993
|
+
const MODULE_HRM_PATTERNS = ["/api/hrm/", "/api/bs/hrm/", "/api/app/hrm/"];
|
|
1994
|
+
const MODULE_PERM_WORDS = ["没有权限", "无权限", "权限不足", "未授权", "无权访问", "无查询权限"];
|
|
1995
|
+
const MODULE_INVOICE_TOKEN_PATH = "/api/inc/proxy/getToken?appType=1";
|
|
1996
|
+
const MODULE_EXIT = { OK: 0, AUTH: 2, JSON: 3, PERM: 4, HRM: 6, HTTP: 7, USAGE: 8 };
|
|
1997
|
+
|
|
1998
|
+
/** 带退出码的错误:main().catch 按 e.exitCode 退出(未设置仍为 1) */
|
|
1999
|
+
class ModuleExit extends Error {
|
|
2000
|
+
constructor(code, message) { super(message); this.exitCode = code; }
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/** json 模式下先向 stdout 打一份结构化错误信封,再带退出码抛出(对齐 call_module.py 的输出约定) */
|
|
2004
|
+
function moduleDie(json, code, msg) {
|
|
2005
|
+
if (json) console.log(JSON.stringify({ ok: false, exit: code, error: msg }, null, 1));
|
|
2006
|
+
throw new ModuleExit(code, msg);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
/** 模块接口调用的 User-Agent:`AgentType=<agentType>,IsAgent=true`
|
|
2010
|
+
* 优先取登录态自带的 UA(weaver-work-cli export-weaver-env 的 WEAVER_USER_AGENT),但**必须是 AgentType 形态**;
|
|
2011
|
+
* 缺失 / 形态不符 / 混入非 ASCII(会致请求头编码失败)时,从 cookie 的 agentType= 推导,再退回 workbuddy。 */
|
|
2012
|
+
function moduleUserAgent() {
|
|
2013
|
+
const raw = String((auth && auth.userAgent) || "").trim();
|
|
2014
|
+
if (raw && !/[^\x00-\x7F]/.test(raw) && /agenttype=/i.test(raw)) return raw;
|
|
2015
|
+
const m = String((auth && auth.cookie) || "").match(/agentType=([^;]+)/i);
|
|
2016
|
+
return "AgentType=" + (m ? m[1].trim() : "workbuddy") + ",IsAgent=true";
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
/** 业务参数 → query 串(数组按多值展开、对象 JSON 化;对齐 urlencode(..., doseq=True)) */
|
|
2020
|
+
function moduleQueryString(obj) {
|
|
2021
|
+
const sp = new URLSearchParams();
|
|
2022
|
+
for (const [k, v] of Object.entries(obj || {})) {
|
|
2023
|
+
if (v === undefined || v === null) continue;
|
|
2024
|
+
if (Array.isArray(v)) for (const x of v) sp.append(k, x !== null && typeof x === "object" ? JSON.stringify(x) : String(x));
|
|
2025
|
+
else sp.append(k, typeof v === "object" ? JSON.stringify(v) : String(v));
|
|
2026
|
+
}
|
|
2027
|
+
return sp.toString();
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
/** 发请求(带超时);网络层失败 → 退出码 7 */
|
|
2031
|
+
async function moduleFetch(method, pathAndQuery, headers, body, timeoutMs) {
|
|
2032
|
+
const ctrl = new AbortController();
|
|
2033
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
2034
|
+
try {
|
|
2035
|
+
const resp = await fetch(BASE + pathAndQuery, {
|
|
2036
|
+
method,
|
|
2037
|
+
headers,
|
|
2038
|
+
...(body !== undefined ? { body } : {}),
|
|
2039
|
+
signal: ctrl.signal,
|
|
2040
|
+
});
|
|
2041
|
+
return { status: resp.status, text: await resp.text() };
|
|
2042
|
+
} catch (e) {
|
|
2043
|
+
const aborted = String((e && e.name) || "") === "AbortError";
|
|
2044
|
+
throw new ModuleExit(MODULE_EXIT.HTTP, "请求失败:" + (aborted ? "超时 " + timeoutMs + "ms" : String((e && e.message) || e)));
|
|
2045
|
+
} finally { clearTimeout(timer); }
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
/** 按已知外层形态找列表:datajson.data / datajson.datas / data.data / data.records / data / datas / list */
|
|
2049
|
+
function moduleRowsOf(obj) {
|
|
2050
|
+
const numish = (v) => (typeof v === "number" || typeof v === "string") ? v : null;
|
|
2051
|
+
if (Array.isArray(obj)) return { rows: obj, total: null };
|
|
2052
|
+
if (!obj || typeof obj !== "object") return { rows: null, total: null };
|
|
2053
|
+
for (const k of ["datajson", "data", "result"]) {
|
|
2054
|
+
const v = obj[k];
|
|
2055
|
+
if (Array.isArray(v)) return { rows: v, total: null };
|
|
2056
|
+
if (v && typeof v === "object") {
|
|
2057
|
+
for (const kk of ["data", "datas", "records", "list", "items", "rows"]) {
|
|
2058
|
+
if (Array.isArray(v[kk])) return { rows: v[kk], total: numish(v.total) };
|
|
2059
|
+
}
|
|
2060
|
+
return { rows: null, total: null };
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
for (const kk of ["datas", "list", "records", "items", "rows"]) {
|
|
2064
|
+
if (Array.isArray(obj[kk])) return { rows: obj[kk], total: numish(obj.total) };
|
|
2065
|
+
}
|
|
2066
|
+
return { rows: null, total: null };
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
/** 取载荷(datajson 优先,其次 data,否则根)与来源名 */
|
|
2070
|
+
function modulePayloadOf(obj) {
|
|
2071
|
+
if (obj && typeof obj === "object" && !Array.isArray(obj)) {
|
|
2072
|
+
for (const k of ["datajson", "data"]) {
|
|
2073
|
+
if (obj[k] !== undefined && obj[k] !== null) return { value: obj[k], via: k };
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
return { value: obj, via: "(root)" };
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
/** 权限/二次验证标志位识别(权限受限 ≠ 无数据;二次验证 ≠ 无数据) */
|
|
2080
|
+
function moduleFlagsOf(obj, payload) {
|
|
2081
|
+
const flags = [];
|
|
2082
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload) && payload.hasSearchRight === false) {
|
|
2083
|
+
flags.push("hasSearchRight=false(权限受限,其余字段必为空 → 立即终止,不得交付为「无数据」)");
|
|
2084
|
+
}
|
|
2085
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload) && payload.pageUid === "checkSecondFail") {
|
|
2086
|
+
flags.push("pageUid=checkSecondFail(二次身份验证:total 可信、明细恒 0 行 → 如实说明,不得当无数据)");
|
|
2087
|
+
}
|
|
2088
|
+
let txt = "";
|
|
2089
|
+
try { txt = JSON.stringify(obj).slice(0, 4000); } catch { txt = ""; }
|
|
2090
|
+
if (MODULE_PERM_WORDS.some((w) => txt.includes(w))) flags.push("响应含权限关键词 → 按「无权限即终止」处理");
|
|
2091
|
+
return flags;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
/** INVOICE 业务接口的发票云 Token(内存透传;🚫 不打印、不落盘) */
|
|
2095
|
+
async function moduleInvoiceToken(json, timeoutMs) {
|
|
2096
|
+
const headers = {
|
|
2097
|
+
"accept": "application/json, text/plain, */*",
|
|
2098
|
+
"langType": "zh_CN",
|
|
2099
|
+
"cookie": auth.cookie,
|
|
2100
|
+
"eteamsid": auth.eteamsid,
|
|
2101
|
+
"user-agent": moduleUserAgent(),
|
|
2102
|
+
};
|
|
2103
|
+
const { status, text } = await moduleFetch("GET", MODULE_INVOICE_TOKEN_PATH, headers, undefined, timeoutMs);
|
|
2104
|
+
let tk = null;
|
|
2105
|
+
try { const j = parseJsonSafe(text); tk = j && j.data && j.data.token; } catch {}
|
|
2106
|
+
if (!tk) moduleDie(json, MODULE_EXIT.HTTP, "未取到发票云 Token(INVOICE 业务接口必需;HTTP " + status + ")");
|
|
2107
|
+
return tk;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
async function cmdModuleCall(args, json) {
|
|
2111
|
+
// ---- 必填与选项(缺参一律退出码 8,对齐 call_module.py)----
|
|
2112
|
+
if (!args["conf-key"] || args["conf-key"] === true) {
|
|
2113
|
+
moduleDie(json, MODULE_EXIT.USAGE, "缺必填参数 --conf-key(用法: module call --conf-key <confKey> --group <模块>)");
|
|
2114
|
+
}
|
|
2115
|
+
if (!args.group || args.group === true) {
|
|
2116
|
+
moduleDie(json, MODULE_EXIT.USAGE, "缺必填参数 --group(模块唯一标识,如 WORKFLOW / EHR / DOCUMENT)");
|
|
2117
|
+
}
|
|
2118
|
+
const confKey = String(args["conf-key"]);
|
|
2119
|
+
const group = String(args.group);
|
|
2120
|
+
const timeoutMs = Math.max(1, parseInt(args.timeout || "60", 10) || 60) * 1000;
|
|
2121
|
+
const limit = (args.limit === undefined || args.limit === true) ? 50 : (parseInt(String(args.limit), 10) || 0);
|
|
2122
|
+
const fields = (typeof args.fields === "string" && args.fields) ? readListArg(args.fields).split(",").map((s) => s.trim()).filter(Boolean) : null;
|
|
2123
|
+
|
|
2124
|
+
// ---- ① 精取该条接口配置(confType 固定 api;confGroup 二级定位必带)----
|
|
2125
|
+
let rec;
|
|
2126
|
+
try { rec = await confGetData("api", confKey, false, group); }
|
|
2127
|
+
catch (e) {
|
|
2128
|
+
const m = String((e && e.message) || e);
|
|
2129
|
+
moduleDie(json, /登录态失效|未取得登录凭据/.test(m) ? MODULE_EXIT.AUTH : MODULE_EXIT.JSON,
|
|
2130
|
+
"conf get 失败:" + m + "(先核对 confKey / confGroup;登录态问题请重新登录)");
|
|
2131
|
+
}
|
|
2132
|
+
let attrs = rec.confAttrs;
|
|
2133
|
+
if (typeof attrs === "string") {
|
|
2134
|
+
try { attrs = parseJsonSafe(attrs, "confAttrs"); }
|
|
2135
|
+
catch { moduleDie(json, MODULE_EXIT.JSON, "confAttrs 二次解析失败(非 JSON 字符串): " + confKey); }
|
|
2136
|
+
}
|
|
2137
|
+
if (!attrs || typeof attrs !== "object" || Array.isArray(attrs)) {
|
|
2138
|
+
moduleDie(json, MODULE_EXIT.JSON, "confAttrs 缺失或形态异常: " + confKey + "(--group " + group + ")");
|
|
2139
|
+
}
|
|
2140
|
+
const apiPath = String(attrs.api_path || rec.confValue || "");
|
|
2141
|
+
if (!apiPath) moduleDie(json, MODULE_EXIT.JSON, "confAttrs 未提供 api_path / confValue: " + confKey);
|
|
2142
|
+
|
|
2143
|
+
// ---- ② HRM 路径黑名单(人员/部门/岗位只走 weaver-work-cli 的 hrm 子命令)----
|
|
2144
|
+
if (MODULE_HRM_PATTERNS.some((p) => apiPath.includes(p))) {
|
|
2145
|
+
moduleDie(json, MODULE_EXIT.HRM,
|
|
2146
|
+
"该接口命中 HRM 路径黑名单(" + apiPath + ")→ 人员/部门/岗位一律走 weaver-work-cli 的 hrm 子命令,禁止直调");
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
const apiName = attrs.api_name || rec.confName || confKey;
|
|
2150
|
+
|
|
2151
|
+
// ---- ③ --show-params:只给入/出参说明与请求示例,不调接口(用于正确组参)----
|
|
2152
|
+
if (args["show-params"]) {
|
|
2153
|
+
const out = {
|
|
2154
|
+
confKey, module: group, api_name: apiName,
|
|
2155
|
+
http_method: attrs.http_method ?? null, api_path: apiPath,
|
|
2156
|
+
business_desc: attrs.business_desc ?? null,
|
|
2157
|
+
api_params: attrs.api_params ?? null,
|
|
2158
|
+
input_params: attrs.input_params ?? null,
|
|
2159
|
+
output_params: attrs.output_params ?? null,
|
|
2160
|
+
request_example: attrs.request_example ?? null,
|
|
2161
|
+
};
|
|
2162
|
+
console.log(JSON.stringify(out, null, 1));
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
// ---- ④ 组参:--args(@文件 / 内联)+ --query(k=v&k2=v2)+ --header / --headers ----
|
|
2167
|
+
let bizArgs = {};
|
|
2168
|
+
if (args.args !== undefined && args.args !== true) {
|
|
2169
|
+
try { bizArgs = readJsonArg(String(args.args), "--args"); }
|
|
2170
|
+
catch (e) { moduleDie(json, MODULE_EXIT.USAGE, "--args 读取/解析失败:" + ((e && e.message) || e)); }
|
|
2171
|
+
}
|
|
2172
|
+
if (!bizArgs || typeof bizArgs !== "object" || Array.isArray(bizArgs)) {
|
|
2173
|
+
moduleDie(json, MODULE_EXIT.USAGE, "--args 必须是 JSON 对象");
|
|
2174
|
+
}
|
|
2175
|
+
if (typeof args.query === "string" && args.query) {
|
|
2176
|
+
for (const pair of args.query.split("&")) {
|
|
2177
|
+
if (!pair) continue;
|
|
2178
|
+
const i = pair.indexOf("=");
|
|
2179
|
+
if (i > 0) bizArgs[pair.slice(0, i)] = pair.slice(i + 1);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
const extraHeaders = {};
|
|
2183
|
+
if (args.headers !== undefined && args.headers !== true) {
|
|
2184
|
+
let h;
|
|
2185
|
+
try { h = readJsonArg(String(args.headers), "--headers"); }
|
|
2186
|
+
catch (e) { moduleDie(json, MODULE_EXIT.USAGE, "--headers 读取/解析失败:" + ((e && e.message) || e)); }
|
|
2187
|
+
if (h && typeof h === "object" && !Array.isArray(h)) Object.assign(extraHeaders, h);
|
|
2188
|
+
}
|
|
2189
|
+
if (typeof args.header === "string" && args.header.includes(":")) {
|
|
2190
|
+
const i = args.header.indexOf(":");
|
|
2191
|
+
extraHeaders[args.header.slice(0, i).trim()] = args.header.slice(i + 1).trim();
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
const declared = String(args.method || attrs.http_method || "POST").toUpperCase();
|
|
2195
|
+
if (declared !== "GET" && declared !== "POST" && declared !== "FORM") {
|
|
2196
|
+
moduleDie(json, MODULE_EXIT.USAGE, "--method 只支持 GET / POST / FORM(当前: " + args.method + ")");
|
|
2197
|
+
}
|
|
2198
|
+
const effMethod = declared === "FORM" ? "POST" : declared;
|
|
2199
|
+
const isForm = declared === "FORM" || /x-www-form-urlencoded/i.test(String(attrs.api_definition || ""));
|
|
2200
|
+
|
|
2201
|
+
let qs = "", body;
|
|
2202
|
+
if (effMethod === "GET") {
|
|
2203
|
+
const q = moduleQueryString(bizArgs);
|
|
2204
|
+
qs = q ? "?" + q : "";
|
|
2205
|
+
} else {
|
|
2206
|
+
body = isForm ? moduleQueryString(bizArgs) : JSON.stringify(bizArgs);
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
// ---- ⑤ --dry-run:只组装不发送(凭据一律脱敏)----
|
|
2210
|
+
if (args["dry-run"]) {
|
|
2211
|
+
const masked = {};
|
|
2212
|
+
for (const [k, v] of Object.entries(extraHeaders)) masked[k] = /^token$/i.test(k) ? "<脱敏>" : v;
|
|
2213
|
+
const out = {
|
|
2214
|
+
ok: true, confKey, api_name: apiName, method: effMethod,
|
|
2215
|
+
url: "<baseUrl>" + apiPath + qs,
|
|
2216
|
+
body: bizArgs,
|
|
2217
|
+
headers: {
|
|
2218
|
+
Cookie: "<脱敏>", eteamsid: "<脱敏>", "User-Agent": "<AgentType=…,IsAgent=true>",
|
|
2219
|
+
"Content-Type": effMethod === "POST" ? (isForm ? "application/x-www-form-urlencoded" : "application/json;charset=UTF-8") : null,
|
|
2220
|
+
...masked,
|
|
2221
|
+
},
|
|
2222
|
+
note: "--dry-run:未发送请求",
|
|
2223
|
+
};
|
|
2224
|
+
console.log(JSON.stringify(out, null, 1));
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
// ---- ⑥ 发请求 ----
|
|
2229
|
+
const headers = {
|
|
2230
|
+
"accept": "application/json, text/plain, */*",
|
|
2231
|
+
"langType": "zh_CN",
|
|
2232
|
+
"cookie": auth.cookie,
|
|
2233
|
+
"eteamsid": auth.eteamsid,
|
|
2234
|
+
"ebbusinessid": EB_BIZ_ID,
|
|
2235
|
+
"user-agent": moduleUserAgent(),
|
|
2236
|
+
"timezoneoffset": "-480",
|
|
2237
|
+
};
|
|
2238
|
+
if (effMethod === "POST") headers["Content-Type"] = isForm ? "application/x-www-form-urlencoded" : "application/json;charset=UTF-8";
|
|
2239
|
+
// INVOICE:业务接口除通用三件套外必须带发票云 token(先取换、再允许 --header 覆盖)
|
|
2240
|
+
if (group.toUpperCase() === "INVOICE" || String(attrs.module_key || "").toUpperCase() === "INVOICE") {
|
|
2241
|
+
headers["token"] = await moduleInvoiceToken(json, timeoutMs);
|
|
2242
|
+
}
|
|
2243
|
+
Object.assign(headers, extraHeaders);
|
|
2244
|
+
|
|
2245
|
+
const { status, text } = await moduleFetch(effMethod, apiPath + qs, headers, body, timeoutMs);
|
|
2246
|
+
|
|
2247
|
+
if (status === 401) {
|
|
2248
|
+
moduleDie(json, MODULE_EXIT.AUTH,
|
|
2249
|
+
"登录态失效(HTTP 401):凭据来源=" + ((auth && auth.source) || "未知") + ";请执行 `weaver-work-cli auth login --base-url <域名>` 重新登录");
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
let obj = null;
|
|
2253
|
+
try { obj = parseJsonSafe(text); } catch {}
|
|
2254
|
+
if (obj === null) {
|
|
2255
|
+
// 非 JSON:原样输出前 2000 字符(2xx 视为正常,其余按请求失败)
|
|
2256
|
+
if (json) console.log(JSON.stringify({ ok: status >= 200 && status < 300, api_name: apiName, http: status, nonJson: text.slice(0, 2000) }, null, 1));
|
|
2257
|
+
else console.log(text.slice(0, 2000));
|
|
2258
|
+
if (status < 200 || status >= 300) throw new ModuleExit(MODULE_EXIT.HTTP, "HTTP " + status + " 且响应非 JSON(已原样输出前 2000 字符)");
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
// ---- ⑦ --raw:原样输出响应体 ----
|
|
2263
|
+
if (args.raw) {
|
|
2264
|
+
console.log(JSON.stringify(obj, null, 1));
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// ---- ⑧ 提取精简结果 ----
|
|
2269
|
+
const payload = modulePayloadOf(obj);
|
|
2270
|
+
const flags = moduleFlagsOf(obj, payload.value);
|
|
2271
|
+
const { rows, total } = moduleRowsOf(obj);
|
|
2272
|
+
const out = { ok: true, api_name: apiName, http: status, via: payload.via };
|
|
2273
|
+
let projected = null;
|
|
2274
|
+
if (rows !== null) {
|
|
2275
|
+
const shown = limit > 0 ? rows.slice(0, limit) : rows;
|
|
2276
|
+
projected = fields
|
|
2277
|
+
? shown.map((r) => {
|
|
2278
|
+
const o = {};
|
|
2279
|
+
if (r && typeof r === "object") for (const k of fields) if (k in r) o[k] = r[k];
|
|
2280
|
+
return o;
|
|
2281
|
+
})
|
|
2282
|
+
: shown;
|
|
2283
|
+
out.total = total;
|
|
2284
|
+
out.rows = rows.length;
|
|
2285
|
+
out.shown = projected.length;
|
|
2286
|
+
out.data = projected;
|
|
2287
|
+
} else {
|
|
2288
|
+
out.data = payload.value;
|
|
2289
|
+
}
|
|
2290
|
+
if (flags.length) out.flags = flags;
|
|
2291
|
+
|
|
2292
|
+
if (json) {
|
|
2293
|
+
console.log(JSON.stringify(out, null, 1));
|
|
2294
|
+
} else {
|
|
2295
|
+
console.log("== " + apiName + " ==");
|
|
2296
|
+
console.log(effMethod + " " + apiPath + " → HTTP " + status + "(via=" + payload.via + ")");
|
|
2297
|
+
if (rows !== null) {
|
|
2298
|
+
console.log("rows=" + rows.length
|
|
2299
|
+
+ (total !== null && total !== undefined ? " / total=" + total : "")
|
|
2300
|
+
+ (limit > 0 && rows.length > limit ? "(已截断到 " + limit + " 行)" : ""));
|
|
2301
|
+
printHuman("", projected);
|
|
2302
|
+
} else {
|
|
2303
|
+
console.log(JSON.stringify(payload.value, null, 1));
|
|
2304
|
+
}
|
|
2305
|
+
for (const f of flags) console.log("[!] " + f);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
// 权限受限是终端态:结果照常给出(供调用方留证),但退出码必须是非 0(对齐 call_module.py)
|
|
2309
|
+
if (flags.some((f) => f.startsWith("hasSearchRight=false"))) {
|
|
2310
|
+
throw new ModuleExit(MODULE_EXIT.PERM, "权限受限:hasSearchRight=false(立即终止,不重试、不换参、不交付为「无数据」)");
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
1996
2314
|
// ---------------- attend:考勤 REST 接口 ----------------
|
|
1997
2315
|
// 2026-09-07:封装 /api/attend/web/attendInfoV2/getAttendInfoStatis(假期余额统计,hr-attendance 场景六病假/事假)。
|
|
1998
2316
|
// 请求参数 {beginDate, endDate, userId};返回 data.vacationBalanceList[]:{id,title,data:[{name,value,key}]},
|
|
@@ -2357,6 +2675,62 @@ async function expRollback(args, json) {
|
|
|
2357
2675
|
console.log("== 回滚成功 =="); console.log(JSON.stringify(r.data ?? {}, null, 1));
|
|
2358
2676
|
}
|
|
2359
2677
|
|
|
2678
|
+
// ---------------- refusal:拒答话术 getRefusalSpeechTechnique(2026-10-13 新增)----------------
|
|
2679
|
+
// 端点:/api/dw/newetl/chatBi/getRefusalSpeechTechnique
|
|
2680
|
+
// 入参:仅 1 个 —— type(场景/类型标识,取值由后端定义,原样透传,不做白名单校验)
|
|
2681
|
+
// 返回:对象,其中 data 为需要使用的**话术字符串**(可能为空串 = 该 type 无话术)
|
|
2682
|
+
// 通道选择:仿照 /api/dw/newetl/chatBi/ds/fieldsForDSL 的调用方式(postMultipart 表单)。
|
|
2683
|
+
// 2026-10-13 实测(type=Extraterritorial,泛微网络租户):**POST multipart 表单字段 type 即命中**,
|
|
2684
|
+
// 返回体形如 { data: "<话术字符串>", actionMsg: { code: 0, message: "成功" } }。
|
|
2685
|
+
// 默认仍为 auto「先 POST 表单 → 判定无效才回退 GET ?type=」,任一条拿到 data 即止
|
|
2686
|
+
// (正常路径只发 1 次请求,回退仅在 POST 拿不到 data 时触发);可用 --http post|get 强制单一通道。
|
|
2687
|
+
const REFUSAL_PATH = "/api/dw/newetl/chatBi/getRefusalSpeechTechnique";
|
|
2688
|
+
const REFUSAL_HTTP_MODES = ["auto", "post", "get"];
|
|
2689
|
+
// 有效返回判定:对象且带 data 字段(data 允许为空串 —— 语义是「该 type 无话术」,不等于失败)
|
|
2690
|
+
function refusalHasData(r) {
|
|
2691
|
+
return !!r && typeof r === "object" && r.data !== undefined && r.data !== null;
|
|
2692
|
+
}
|
|
2693
|
+
async function refusalRequest(mode, type) {
|
|
2694
|
+
if (mode === "get") return getUrl(REFUSAL_PATH + "?type=" + encodeURIComponent(type));
|
|
2695
|
+
return postMultipart(REFUSAL_PATH, { type }); // 与 fieldsForDSL 同口径:multipart 表单字段 type
|
|
2696
|
+
}
|
|
2697
|
+
// 返回 { r, mode }:r = 命中的响应体,mode = 实际生效的通道(post/get),供排障展示
|
|
2698
|
+
async function refusalCall(type, httpMode) {
|
|
2699
|
+
const modes = httpMode === "auto" ? ["post", "get"] : [httpMode];
|
|
2700
|
+
const tried = [];
|
|
2701
|
+
for (const mode of modes) {
|
|
2702
|
+
let r;
|
|
2703
|
+
try {
|
|
2704
|
+
r = await refusalRequest(mode, type);
|
|
2705
|
+
} catch (e) {
|
|
2706
|
+
const msg = (e && e.message) || String(e);
|
|
2707
|
+
if (/登录态失效|401/.test(msg)) throw e; // 凭据问题与「通道选哪条」无关,不换通道重试(避免重复报错)
|
|
2708
|
+
tried.push(mode + ": " + msg);
|
|
2709
|
+
continue;
|
|
2710
|
+
}
|
|
2711
|
+
if (refusalHasData(r)) {
|
|
2712
|
+
if (process.env.E10_DATA_DEBUG) console.error("[debug] getRefusalSpeechTechnique 命中通道=" + mode);
|
|
2713
|
+
return { r, mode };
|
|
2714
|
+
}
|
|
2715
|
+
tried.push(mode + ": code=" + (r && r.code) + " msg=" + ((r && r.msg) || "(无 data 字段)"));
|
|
2716
|
+
}
|
|
2717
|
+
throw new Error("getRefusalSpeechTechnique 调用失败(type=" + type + ")已尝试 " + modes.length + " 种通道 → " + tried.join(" | "));
|
|
2718
|
+
}
|
|
2719
|
+
async function cmdRefusalGet(args, json) {
|
|
2720
|
+
const type = need("type", args);
|
|
2721
|
+
const httpMode = args.http === undefined ? "auto" : String(args.http).toLowerCase();
|
|
2722
|
+
if (!REFUSAL_HTTP_MODES.includes(httpMode)) throw new Error("--http 值必须为 " + REFUSAL_HTTP_MODES.join("|") + "(当前: " + args.http + ")");
|
|
2723
|
+
const { r } = await refusalCall(type, httpMode);
|
|
2724
|
+
if (json) return r; // 原样输出整个响应对象(含 data)
|
|
2725
|
+
const d = r.data;
|
|
2726
|
+
if (typeof d === "string") {
|
|
2727
|
+
if (!d.trim()) { console.log("(空:type=" + type + " 无话术内容)"); return; }
|
|
2728
|
+
console.log(d);
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
console.log(typeof d === "object" ? JSON.stringify(d, null, 1) : String(d));
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2360
2734
|
// ---------------- main ----------------
|
|
2361
2735
|
function usage() {
|
|
2362
2736
|
console.log(`e10-data CLI(data-assistant 数据接口统一命令行,2026-09-03 扩展)
|
|
@@ -2389,19 +2763,22 @@ ds 数据源(业务模块)取数与元数据
|
|
|
2389
2763
|
ds describe --group <gid> --table <tid> [--app-id <id>] [--skill <skillKey,默认 data-source-query>]
|
|
2390
2764
|
[--require] [--source-type LOGIC]
|
|
2391
2765
|
# 一次**并发**取回两件套:字段 + 词库(两者入参同为 (groupId, tableId),互不依赖)
|
|
2392
|
-
#
|
|
2766
|
+
# 不做权限校验:EB 分组取数不再做权限前置校验,appId 直接取 ebdf_obj.app_id
|
|
2393
2767
|
# --require:任一路失败即报错退出
|
|
2394
2768
|
ds tables --group <gid> [--name 关键词] [--app-id <id>]
|
|
2395
2769
|
ds fields --group <gid> --table <tid> [--app-id <id>]
|
|
2396
2770
|
ds fields-dsl --group <gid> --table <tid> [--app-id <id>] # 同 fields,接口 /api/dw/newetl/chatBi/ds/fieldsForDSL
|
|
2397
|
-
ds list --group <gid> --table <tid> --fields a,b [--where @f|json] [--order "f asc"] [--page N] [--size N] [--app-id <id>]
|
|
2398
|
-
ds chart --group <gid> --table <tid> --group-by g1,g2 --stat @f.json [--where ...] [--app-id <id>]
|
|
2771
|
+
ds list --group <gid> --table <tid> --fields a,b|@f [--where @f|json] [--order "f asc"|@f] [--page N] [--size N] [--app-id <id>]
|
|
2772
|
+
ds chart --group <gid> --table <tid> --group-by g1,g2|@f --stat @f.json [--where ...] [--app-id <id>]
|
|
2399
2773
|
ds stat --group <gid> --table <tid> --stat @f.json [--where ...] [--app-id <id>]
|
|
2400
2774
|
ds count --group <gid> --table <tid> [--where ...] [--app-id <id>]
|
|
2775
|
+
# 逗号列表(--fields / --group-by / --order)建议一律走 @文件:逗号不出现在命令行上,避免被 shell
|
|
2776
|
+
# (尤其 Windows 交互式 PowerShell 把未加引号的 a,b,c 当数组拆开)拆分 → 拆分后不报错,会静默丢列 / 静默 0 行
|
|
2777
|
+
# 文件内容两种形态均可:逗号字符串 "a,b,c" 或 JSON 数组 ["a","b","c"]
|
|
2401
2778
|
(ds 通用 --source-type LOGIC|ETEAMS,默认 LOGIC)
|
|
2402
2779
|
(ds 取数的 appId:一律**显式传入** --app-id <id>(v0.1.14 起 CLI 不再自动反查)。
|
|
2403
|
-
EB 建模表单(groupId=weaver-ebuilder-form-service)必须带:该值取自
|
|
2404
|
-
|
|
2780
|
+
EB 建模表单(groupId=weaver-ebuilder-form-service)必须带:该值取自 ebdf_obj.app_id
|
|
2781
|
+
(obj_id = ebdf_obj.id = 数据源 tableId;2026-10-09 起不再做 EB 权限前置校验);
|
|
2405
2782
|
其他数据集可不传,CLI 不拦截、以接口报错为准;--app-id auto / --no-app-id 已废弃,等同不传)
|
|
2406
2783
|
--where 过滤条件(v0.1.8 起支持树形 OR,递归):
|
|
2407
2784
|
数组 = AND 根: [{"fieldId":"f1","conditionType":"Date","compareType":"range","conditionValue":"2026-09~2026-10"},{"fieldId":"f2",...}]
|
|
@@ -2444,11 +2821,26 @@ exp 专家包版本管理(expert-repository/v1,响应以 data.actionMsg.
|
|
|
2444
2821
|
exp rollback --expert <code> --version vX.Y.Z
|
|
2445
2822
|
attend 考勤 REST 接口(2026-09-07 新增,hr-attendance 病假/事假等假期余额)
|
|
2446
2823
|
attend statis [--user <人员ID>] [--begin 2026-09-01] [--end 2026-09-30] # getAttendInfoStatis 假期余额统计;--user 默认当前登录用户,--begin/--end 默认当月
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
#
|
|
2824
|
+
module 模块配置接口调用(配置中心驱动;**已确定「哪个模块的哪条具体接口」之后**用它取数,2026-09-28 新增)
|
|
2825
|
+
module call --conf-key <confKey> --group <模块> [--args '<JSON>'|--args @params.json]
|
|
2826
|
+
[--query "k=v&k2=v2"] [--method GET|POST|FORM] [--header 'K: V'|--headers '<JSON>'|@file]
|
|
2827
|
+
[--limit 50] [--fields a,b|@f] [--raw] [--show-params] [--dry-run] [--timeout 60] [--json]
|
|
2828
|
+
# 一条命令完成:精取该条 confAttrs → 按 http_method/input_params 组参 → 发请求
|
|
2829
|
+
# (自动禁代理 + Cookie/eteamsid/UA 三件套)→ 按外层结构(datajson/data/records/裸数组)提取精简结果
|
|
2830
|
+
# 与技能自带 python 调用器 call_module.py 等价(参数名/退出码/提取口径全对齐),但复用本 CLI 凭据缓存与
|
|
2831
|
+
# conf 通道、零临时文件 —— 调用器首选本命令,call_module.py 仅作兜底
|
|
2832
|
+
# --show-params:只打印入/出参说明与请求示例(不调接口),用于正确组参
|
|
2833
|
+
# --dry-run:只组装请求不发送(凭据脱敏);--limit 0 = 不截断
|
|
2834
|
+
# --query 多对用 & 连接;INVOICE 自动换取发票云 token(内存透传,不打印不落盘)
|
|
2835
|
+
# 退出码:0 正常(**空结果也算正常**)|2 未登录·凭据失效|3 非 JSON·confAttrs 异常
|
|
2836
|
+
# |4 权限受限(hasSearchRight=false,终端态)|6 命中 HRM 路径黑名单|7 请求失败|8 用法错误
|
|
2837
|
+
# 示例: module call --conf-key POST_EHR_GET_ATTEND_INFO_STATIS --group EHR --args @params.json --json
|
|
2838
|
+
refusal 拒答话术(chatBi,2026-10-13 新增)
|
|
2839
|
+
refusal get --type <type> [--http auto|post|get] [--json] # 接口 /api/dw/newetl/chatBi/getRefusalSpeechTechnique
|
|
2840
|
+
# 入参仅 type(场景/类型标识,取值由后端定义,原样透传);返回对象的 data = 话术字符串
|
|
2841
|
+
# --http:auto(默认)= 先 POST multipart 表单(同 ds fields-dsl 口径),
|
|
2842
|
+
# 返回不含 data 时自动回退 GET ?type=;post = 仅 POST 表单;get = 仅 GET query
|
|
2843
|
+
# 人读输出直接打印 data 话术正文(空串 → 提示该 type 无话术);--json 原样输出整个响应对象
|
|
2452
2844
|
认证: 加解密由 e10-login 负责(本 CLI 不参与);凭据出口 = e10-login auth/whoami(自动同步镜像);皆无请先 e10-login oidc
|
|
2453
2845
|
(无浏览器/无 python 环境:login xiaoe-env 用环境变量 token 直接登录)`);
|
|
2454
2846
|
}
|
|
@@ -2510,16 +2902,23 @@ async function main() {
|
|
|
2510
2902
|
case "rollback": result = await expRollback(args, json); break;
|
|
2511
2903
|
default: usage(); process.exit(1);
|
|
2512
2904
|
}
|
|
2905
|
+
} else if (family === "refusal") {
|
|
2906
|
+
if (sub === "get") result = await cmdRefusalGet(args, json);
|
|
2907
|
+
else { usage(); process.exit(1); }
|
|
2513
2908
|
} else if (family === "attend") {
|
|
2514
2909
|
if (sub === "statis") result = await cmdAttendStatis(args, json);
|
|
2515
2910
|
else { usage(); process.exit(1); }
|
|
2516
|
-
} else if (family === "
|
|
2911
|
+
} else if (family === "module") {
|
|
2517
2912
|
if (!sub) { usage(); process.exit(1); }
|
|
2518
2913
|
switch (sub) {
|
|
2519
|
-
case "
|
|
2914
|
+
case "call": result = await cmdModuleCall(args, json); break;
|
|
2520
2915
|
default: usage(); process.exit(1);
|
|
2521
2916
|
}
|
|
2522
2917
|
} else { usage(); process.exit(1); }
|
|
2523
2918
|
if (json && result !== undefined) console.log(JSON.stringify(result, null, 1));
|
|
2524
2919
|
}
|
|
2525
|
-
|
|
2920
|
+
// 退出码:默认 1(用法/未知错误);错误对象带 exitCode 时按其退出(module call 用到 2/3/4/6/7/8)
|
|
2921
|
+
main().catch((e) => {
|
|
2922
|
+
console.error("ERROR: " + ((e && e.message) || e));
|
|
2923
|
+
process.exit(Number.isInteger(e && e.exitCode) ? e.exitCode : 1);
|
|
2924
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "e10-data",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):登录(login xiaoe-env,环境变量 XiaoE 全局 token 换 ETEAMSID,免浏览器/免 python)/DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf,支持 confGroup 分组)
|
|
3
|
+
"version": "0.1.23",
|
|
4
|
+
"description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):登录(login xiaoe-env,环境变量 XiaoE 全局 token 换 ETEAMSID,免浏览器/免 python)/DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf,支持 confGroup 分组)/专家包版本管理;ds 取数(list/chart/stat/count)的 appId 由调用方显式传入 --app-id(v0.1.14 起 CLI 不再自动反查;EB 建模表单 weaver-ebuilder-form-service 必须带,appId 取自 ebdf_obj.app_id;2026-10-09 起不再做 EB 权限前置校验);认证凭据**唯一来源 = weaver-work-cli**(通过 `auth export-weaver-env` 命令出口取明文凭据,**不设 e10-login 回退**;加解密由该 CLI 自行负责,本 CLI 不实现任何加解密;仅 login xiaoe-env 在无凭据时自行换 token,写 profile 仍交 e10-login set)。v0.1.17 性能改造:免凭据命令(--version/--help/未知命令)提前返回、直连 e10-login JS 入口(去掉 shell:true 的 cmd.exe 开销)、新增凭据进程间 TTL 缓存(不落明文);v0.1.19(2026-09-24)新增 ds 发现层聚合 groups/find/describe(进程内并发,全 58 分组扫表实测 ~5.5s),并修复 ds tables --name 因 listByPage 端点切换导致的关键词过滤静默失效;v0.1.18(2026-09-24)新增 ds fields-dsl(接口 /api/dw/newetl/chatBi/ds/fieldsForDSL),并移除凭据明文镜像同步(不再写 ~/.workbuddy/e10-login-cache.md);v0.1.20(2026-09-24)凭据来源切换为 weaver-work-cli 唯一来源、移除 e10-login 回退,并补齐 userId/tenantKey 传递(修复 attend statis 等命令报「缺少查询人员」的问题)。v0.1.21(2026-09-28)新增 module call —— 模块配置接口调用(配置中心驱动):已确定「哪个模块的哪条具体接口」之后的一条命令取数入口(精取该条 confAttrs → 按 http_method/input_params 组参 → 发请求(自动禁代理 + Cookie/eteamsid/UA 三件套)→ 提取精简结果),与技能自带 script/call_module.py 等价(参数名/退出码/提取口径全对齐),复用本 CLI 凭据缓存与 conf 通道、零临时文件;同时 auth 归一化与凭据缓存补齐 userAgent(供 AgentType UA 使用,缺失时从 cookie 的 agentType= 推导)。v0.1.22(2026-10-10):移除 `eb perm-page` 命令(EB 建模表单取数不再做权限前置校验,appId 直接取 ebdf_obj.app_id);`--fields`/`--group-by`/`--order` 等逗号列表支持 `@文件`(逗号字符串或 JSON 数组,与 --where/--stat/--dsl 一致,规避 shell 拆分导致的静默丢列/0 行)。v0.1.23(2026-10-13)新增 `refusal get` —— 拒答话术 getRefusalSpeechTechnique(入参仅 type,返回对象的 data = 话术字符串;默认 auto 通道 = POST multipart 表单优先、返回不含 data 时自动回退 GET ?type=,可用 --http post|get 锁定单通道)。",
|
|
5
5
|
"author": "e10-dw",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|