e10-data 0.1.9 → 0.1.12
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/README.md +33 -2
- package/bin/e10-data.mjs +239 -68
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ npm i -g e10-data # 全局安装
|
|
|
9
9
|
e10-data --version # 验证
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
> 依赖:认证复用 `weaver-e10-login`
|
|
12
|
+
> 依赖:认证复用 `weaver-e10-login` 的登录态。**加解密全部由 `e10-login` 负责,本 CLI 不实现任何加解密**(auth 文件由 e10-login 自己的密钥体系加密:1.5.0 起为「每机随机密钥 + OS keychain」,早期版本为固定 seed,只有 e10-login 解得开)。CLI 通过 e10-login 的命令出口取明文凭据,优先级:**① `e10-login auth --json`**(推荐的正式契约)→ **② `e10-login whoami --json`**(其 JSON 带 `cookies` 时)→ **③ `e10-login whoami`**(文本解析,当前实际路径)→ **④ 明文镜像 `~/.workbuddy/e10-login-cache.md`**(兜底,命中时打印告警)。请先安装并登录 weaver-e10-login(`e10-login oidc`)。
|
|
13
13
|
|
|
14
14
|
## 命令总览
|
|
15
15
|
|
|
@@ -29,7 +29,7 @@ e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-atte
|
|
|
29
29
|
- `--json`:原样输出接口 JSON(供脚本/Agent 消费);默认人读输出。
|
|
30
30
|
- 复杂 JSON/中文参数:推荐 `@文件` 承载(如 `--dsl @q.json`、`--where @cond.json`)。
|
|
31
31
|
- ⚠️ 大整数精度(自动处理):E10 的 ID 多为 19 位,超过 JS 安全整数(2^53-1≈9e15)。CLI 解析 JSON 时**自动把超范围裸整数转成字符串**再发请求/输出(实测后端接受字符串做 =/IN/BETWEEN 数值比较),无需手动加引号,也不误伤字符串正文。
|
|
32
|
-
-
|
|
32
|
+
- 认证:**加解密由 `e10-login` 负责(本 CLI 不参与)**。凭据出口优先级:`e10-login auth --json` → `e10-login whoami --json` → `e10-login whoami`(文本)→ 明文镜像 `~/.workbuddy/e10-login-cache.md`(兜底,命中时告警)。命中的出口会记入 `~/.e10-cli/.e10-data-auth-mode`(只存模式名、不存凭据),下次优先复用。环境变量:`E10_DATA_AUTH_PROBE=1` 强制重新探测、`E10_DATA_AUTH_MODE=<模式名>` 手动指定、`E10_DATA_QUIET=1` 抑制告警(告警只走 stderr)。
|
|
33
33
|
- 版本更新:`npm i -g e10-data@latest`;查看源上最新 `npm view e10-data version`。
|
|
34
34
|
|
|
35
35
|
## 用法示例
|
|
@@ -112,6 +112,37 @@ e10-data conf delete GET_WORKFLOW_LIST --type api --group workflow
|
|
|
112
112
|
- 同一 `confKey` 可存在于多个 `confGroup`;`conf batch` / `batch-file` 的返回映射在带分组时以 `组|键` 为键,不会互相覆盖。
|
|
113
113
|
- `--json` 输出:`conf group` 返回配置数组(带 `--key` 时返回单条),`conf batch` 返回映射对象。
|
|
114
114
|
|
|
115
|
+
## ds appId 自动解析(v0.1.11 新增)
|
|
116
|
+
|
|
117
|
+
ChatBI 数据源取数接口(`getListDatas` / `getChartDatas` / `getStatDatas` / `getCountData`)对**部分 EB 应用(独立部署)**的数据集,必须在 `queryDto` 中携带 `appId`,否则后端一律返回 `code=500 请求失败`(实测:小餐厅应用 `840360791696547845` 名下 4 张表不传全 500、传即 200;低代码大赛等应用不传也正常)。
|
|
118
|
+
|
|
119
|
+
`e10-data ds list|chart|stat|count` 现**默认自动解析并带上 appId**,解析顺序:
|
|
120
|
+
|
|
121
|
+
1. `--app-id <id>` 显式指定(最高优先,0 额外请求);
|
|
122
|
+
2. 本地缓存 `~/.e10-cli/.e10-data-ds-appid.json`(key = `sourceType|groupId|tableId`,TTL 7 天);
|
|
123
|
+
3. `ebdf_obj`(DSL 查 `ec_edcapp.ebdf_obj`,`id = tableId` → `app_id`)—— EB 表单权威来源,请求最小;
|
|
124
|
+
4. `ds tables`(`listByPage`)按 id 匹配,命中后把**整组** `id→appId` 映射一次性写入缓存;
|
|
125
|
+
5. `ds fields` 扫字段 `config.appId`(Ebuilder 类字段携带);
|
|
126
|
+
6. 均无 → 不带 appId 发送(兼容旧行为)。
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# 默认:自动解析(推荐)
|
|
130
|
+
e10-data ds count --group weaver-ebuilder-form-service --table 840360795890851856
|
|
131
|
+
|
|
132
|
+
# 显式指定(跳过所有解析,最快)
|
|
133
|
+
e10-data ds count --group weaver-ebuilder-form-service --table 840360795890851856 --app-id 840360791696547845
|
|
134
|
+
|
|
135
|
+
# 完全跳过自动解析(明确不需要 appId 的数据集,省一次解析往返)
|
|
136
|
+
e10-data ds count --group weaver-ebuilder-form-service --table 1081139888007225347 --no-app-id
|
|
137
|
+
|
|
138
|
+
# 查看某张表的 appId:ds tables 输出新增 appId 列;ds fields 命中时在表头打印 appId=…
|
|
139
|
+
e10-data ds tables --group weaver-ebuilder-form-service --name 低代码大赛
|
|
140
|
+
e10-data ds fields --group weaver-ebuilder-form-service --table 840360795890851856
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
> ⚠️ `ds tables`(`listByPage`)**不覆盖全部数据集**:实测「小餐厅」应用的 4 张表在该清单(共 997 条)里查不到,其 appId 由第 3 步 `ebdf_obj` 兜底拿到——故解析顺序把 `ebdf_obj` 置于 `ds tables` 之前。
|
|
144
|
+
> 实测对照(带 appId 与 DSL 口径一致):`ds count` + `ydlc isNotNull` = **6470**,与 DSL 同口径结果一致;`ds chart` 按区域分组 = 上海 5123 / 北京 165 / 区域空 1182。
|
|
145
|
+
|
|
115
146
|
## ds --where 过滤条件(v0.1.8 起支持树形 OR)
|
|
116
147
|
|
|
117
148
|
`e10-data ds list|chart|stat|count --where` 接收**条件数组**或**完整条件树**,均可递归嵌套表达 AND/OR:
|
package/bin/e10-data.mjs
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* 覆盖(数据查询链路全部家族;登录/HRM 复用 e10-login / hrm-cli,checkEbModulePermission 已废弃不提供):
|
|
7
7
|
* e10-data query DSL 查询 executeQueryDsl(conn/model/schema/tableId/dslQuery/debug)
|
|
8
8
|
* e10-data ds tables|fields|list|chart|stat|count 数据源(业务模块)取数与元数据
|
|
9
|
+
* 取数(list/chart/stat/count)自动解析并携带 queryDto.appId(v0.1.12):
|
|
10
|
+
* 顺序 chat_bi_eb_config_map(dw,按 obj_id) → ebdf_obj(ec_edcapp,按 id),实时查、不缓存;
|
|
11
|
+
* --app-id <id> 显式指定 / --app-id auto 强制自动 / --no-app-id 跳过(最快)
|
|
9
12
|
* e10-data doc count|list 文档查询 documentListCount / documentList
|
|
10
13
|
* e10-data noun list 词库 getChatDocsBySkillKey
|
|
11
14
|
* e10-data field id2name|name2id 关联字段翻译 testRemoteBrowser / complete
|
|
@@ -15,7 +18,9 @@
|
|
|
15
18
|
* e10-data attend statis 考勤假期余额统计 getAttendInfoStatis(hr-attendance 病假/事假,2026-09-07)
|
|
16
19
|
*
|
|
17
20
|
* 通用:--json 原样输出;复杂 JSON 参数支持 @文件 或 内联(中文走 @文件 更稳)
|
|
18
|
-
*
|
|
21
|
+
* 认证:加解密全部由 e10-login 负责(本 CLI 不实现任何加解密)。凭据出口优先级:
|
|
22
|
+
* ① e10-login auth --json → ② e10-login whoami --json → ③ e10-login whoami(文本解析)
|
|
23
|
+
* → ④ 明文镜像 ~/.workbuddy/e10-login-cache.md(兜底,命中时告警);均不可用时提示先 e10-login oidc 登录
|
|
19
24
|
*
|
|
20
25
|
* 用法示例:
|
|
21
26
|
* node e10-data.mjs query --conn 1144919996804022272 --model dw --schema dw --dsl @q.json
|
|
@@ -29,6 +34,7 @@ import fs from "node:fs";
|
|
|
29
34
|
import os from "node:os";
|
|
30
35
|
import path from "node:path";
|
|
31
36
|
import crypto from "node:crypto";
|
|
37
|
+
import { spawnSync } from "node:child_process";
|
|
32
38
|
import { fileURLToPath } from "node:url";
|
|
33
39
|
|
|
34
40
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -40,70 +46,148 @@ function cliVersion() {
|
|
|
40
46
|
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36";
|
|
41
47
|
const EB_BIZ_ID = "10000000000000000";
|
|
42
48
|
|
|
43
|
-
// ----------------
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
// ---------------- 认证(加解密全部交由 e10-login,本 CLI 不再实现任何加解密)----------------
|
|
50
|
+
// 背景:auth 文件由 e10-login 自己的密钥体系加密 —— e10-login 1.5.0 起为「每机随机密钥 + OS keychain
|
|
51
|
+
// (@napi-rs/keyring,service=e10-cli / account=auth-key;keychain 不可用才落 ~/.e10-cli/.key)」,
|
|
52
|
+
// 早期版本为固定 seed。**只有 e10-login 解得开**,所以本 CLI 只负责「向 e10-login 要明文凭据」,
|
|
53
|
+
// 不再自行解密 —— e10-login 以后更换密钥体系 / 算法 / profile 机制,本 CLI 都无需改动。
|
|
54
|
+
// 凭据出口优先级(命中即返回;命中的"模式名"会记入 ~/.e10-cli/.e10-data-auth-mode,只存模式、不存凭据):
|
|
55
|
+
// ① e10-login auth --json 正式契约(推荐 e10-login 侧新增;一旦可用自动优先)
|
|
56
|
+
// ② e10-login whoami --json 其 JSON 输出带 cookies 字段时可用
|
|
57
|
+
// ③ e10-login whoami 文本输出解析(当前 e10-login 1.5.0 的实际可用路径)
|
|
58
|
+
// ④ ~/.workbuddy/e10-login-cache.md 明文镜像 —— 最后兜底,命中时打印告警(该文件可能已过期)
|
|
59
|
+
// 环境变量:E10_DATA_AUTH_PROBE=1 强制重新探测优先级;E10_DATA_AUTH_MODE=<模式名> 手动指定;
|
|
60
|
+
// E10_DATA_QUIET=1 抑制告警输出(告警一律走 stderr,不污染 --json 的 stdout)。
|
|
61
|
+
const AUTH_MODE_FILE = path.join(os.homedir(), ".e10-cli", ".e10-data-auth-mode");
|
|
62
|
+
|
|
63
|
+
/** 定位当前 profile 的 auth 文件(仅用于"是否已登录"的判断与报错提示,本 CLI 不读其内容做解密) */
|
|
64
|
+
function authFilePath() {
|
|
65
|
+
const home = os.homedir();
|
|
66
|
+
let profile = "default";
|
|
49
67
|
try {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
if (combined.length < IV + TAG + 1) return null;
|
|
53
|
-
const key = crypto.createHash("sha256").update(E10_AUTH_SEED).digest();
|
|
54
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, combined.subarray(0, IV), { authTagLength: TAG });
|
|
55
|
-
decipher.setAuthTag(combined.subarray(combined.length - TAG));
|
|
56
|
-
return JSON.parse(Buffer.concat([decipher.update(combined.subarray(IV, combined.length - TAG)), decipher.final()]).toString("utf-8"));
|
|
57
|
-
} catch { return null; }
|
|
58
|
-
}
|
|
59
|
-
function parseAuthPayload(raw) {
|
|
60
|
-
try { // 兼容两种形态:加密 wrapper({enc:true,data:base64})与明文 JSON({cookies,userId,...})
|
|
61
|
-
const obj = JSON.parse(raw);
|
|
62
|
-
if (obj && obj.enc === true && typeof obj.data === "string") return decryptAuthWrapper(obj);
|
|
63
|
-
if (obj && obj.cookies && (obj.userId || obj.cookies.ETEAMSID)) return obj;
|
|
68
|
+
const activeFile = path.join(home, ".e10-cli", "profile");
|
|
69
|
+
if (fs.existsSync(activeFile)) { const n = fs.readFileSync(activeFile, "utf-8").trim(); if (n) profile = n; }
|
|
64
70
|
} catch {}
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (c && !/langType=/.test(c)) c = "langType=zh_CN; " + c;
|
|
72
|
-
if (c && agent && !/agentType=/.test(c)) c += "; agentType=" + agent;
|
|
73
|
-
if (c && !/isAgent=/.test(c)) c += "; isAgent=true";
|
|
74
|
-
if (!c && eteamsid) c = "langType=zh_CN; ETEAMSID=" + eteamsid + "; agentType=" + agent + "; isAgent=true";
|
|
75
|
-
return { eteamsid, cookie: c };
|
|
76
|
-
}
|
|
77
|
-
function authFromE10Cli() { // ① 权威源:~/.e10-cli/profiles/<active>/auth
|
|
71
|
+
return { profile, file: path.join(home, ".e10-cli", "profiles", profile, "auth") };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let lastLoginDiag = "";
|
|
75
|
+
/** 调用 e10-login(凭据的唯一来源);失败返回 null,并把诊断信息留在 lastLoginDiag */
|
|
76
|
+
function runLoginCli(args) {
|
|
78
77
|
try {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
const r = spawnSync("e10-login", args, { shell: true, encoding: "utf-8", timeout: 20000, windowsHide: true });
|
|
79
|
+
const out = (r.stdout || "").trim();
|
|
80
|
+
if (r.error || r.status !== 0) {
|
|
81
|
+
lastLoginDiag = ((r.stderr || "") + out).trim().split(/\r?\n/).slice(0, 2).join(" / ") || (r.error ? String(r.error.message) : "exit=" + r.status);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return out || null;
|
|
85
|
+
} catch (e) { lastLoginDiag = String((e && e.message) || e); return null; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 归一化 + 校验(缺 baseUrl / eteamsid / cookie 一律视为该出口不可用,继续下一个出口) */
|
|
89
|
+
function normalizeAuth(o) {
|
|
90
|
+
if (!o) return null;
|
|
91
|
+
const baseUrl = String(o.baseUrl || "").trim().replace(/\/+$/, "");
|
|
92
|
+
const eteamsid = String(o.eteamsid || "").trim();
|
|
93
|
+
const cookie = String(o.cookie || "").trim();
|
|
94
|
+
if (!baseUrl || !eteamsid || !cookie) return null;
|
|
95
|
+
if (/^\((未知|空)\)$/.test(eteamsid) || /^\((未知|空)\)$/.test(cookie)) return null;
|
|
96
|
+
return { baseUrl, eteamsid, cookie, userId: String(o.userId || "").trim(), tenantKey: String(o.tenantKey || "").trim(), source: o.source };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** ① e10-login auth --json(正式契约:{v,baseUrl,eteamsId,cookie,userAgent,userId,tenantKey,agentType,profile}) */
|
|
100
|
+
function providerAuthJson() {
|
|
101
|
+
const out = runLoginCli(["auth", "--json"]);
|
|
102
|
+
if (!out) return null;
|
|
103
|
+
let j; try { j = JSON.parse(out); } catch { return null; }
|
|
104
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return null;
|
|
105
|
+
return normalizeAuth({
|
|
106
|
+
baseUrl: j.baseUrl || j.base_url,
|
|
107
|
+
eteamsid: j.eteamsId || j.eteamsid,
|
|
108
|
+
cookie: typeof j.cookie === "string" ? j.cookie : "",
|
|
109
|
+
userId: j.userId || j.employeeId,
|
|
110
|
+
tenantKey: j.tenantKey,
|
|
111
|
+
source: "e10-login auth --json",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** ② e10-login whoami --json(需其 JSON 带 cookies 字符串;当前 1.5.0 未带 → 自动落到 ③) */
|
|
116
|
+
function providerWhoamiJson() {
|
|
117
|
+
const out = runLoginCli(["whoami", "--json"]);
|
|
118
|
+
if (!out) return null;
|
|
119
|
+
let j; try { j = JSON.parse(out); } catch { return null; }
|
|
120
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return null;
|
|
121
|
+
const cookie = typeof j.cookies === "string" ? j.cookies : (typeof j.cookie === "string" ? j.cookie : "");
|
|
122
|
+
return normalizeAuth({
|
|
123
|
+
baseUrl: j.baseUrl,
|
|
124
|
+
eteamsid: j.eteamsId || j.eteamsid,
|
|
125
|
+
cookie,
|
|
126
|
+
userId: j.employeeId || j.userId,
|
|
127
|
+
tenantKey: j.tenantKey,
|
|
128
|
+
source: "e10-login whoami --json",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** ③ e10-login whoami(文本输出解析:employeeId / tenantKey / baseUrl / ETEAMSID / cookies / User-Agent) */
|
|
133
|
+
function providerWhoamiText() {
|
|
134
|
+
const out = runLoginCli(["whoami"]);
|
|
135
|
+
if (!out) return null;
|
|
136
|
+
const m = {};
|
|
137
|
+
for (const line of out.split(/\r?\n/)) {
|
|
138
|
+
const x = line.match(/^\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.+?)\s*$/);
|
|
139
|
+
if (x) m[x[1].toLowerCase()] = x[2];
|
|
140
|
+
}
|
|
141
|
+
return normalizeAuth({
|
|
142
|
+
baseUrl: m["baseurl"],
|
|
143
|
+
eteamsid: m["eteamsid"],
|
|
144
|
+
cookie: m["cookies"],
|
|
145
|
+
userId: m["employeeid"] || m["userid"],
|
|
146
|
+
tenantKey: m["tenantkey"],
|
|
147
|
+
source: "e10-login whoami(文本)",
|
|
148
|
+
});
|
|
92
149
|
}
|
|
93
|
-
|
|
150
|
+
|
|
151
|
+
/** ④ 明文镜像(最后兜底:不涉及任何解密,但可能已过期 → 命中时告警) */
|
|
152
|
+
function providerCacheMd() {
|
|
94
153
|
const p = path.join(os.homedir(), ".workbuddy", "e10-login-cache.md");
|
|
95
154
|
if (!fs.existsSync(p)) return null;
|
|
96
155
|
const txt = fs.readFileSync(p, "utf-8");
|
|
97
156
|
const get = (key) => {
|
|
98
|
-
const
|
|
99
|
-
return
|
|
157
|
+
const mm = txt.match(new RegExp("\\*\\*" + key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\*\\*\\s*[::]\\s*`?([^`\\n]+)`?"));
|
|
158
|
+
return mm ? mm[1].trim() : "";
|
|
100
159
|
};
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
if (!
|
|
104
|
-
return
|
|
160
|
+
const eteamsid = get("ETEAMSID");
|
|
161
|
+
let cookie = get("完整 Cookie 串");
|
|
162
|
+
if (!cookie && eteamsid) cookie = "langType=zh_CN; ETEAMSID=" + eteamsid + "; agentType=workbuddy; isAgent=true";
|
|
163
|
+
return normalizeAuth({
|
|
164
|
+
baseUrl: get("baseUrl"), eteamsid, cookie,
|
|
165
|
+
userId: get("userId") || get("employeeId"), tenantKey: get("tenantKey"),
|
|
166
|
+
source: "明文镜像 ~/.workbuddy/e10-login-cache.md(可能已过期)",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const AUTH_PROVIDERS = [
|
|
171
|
+
["auth-json", providerAuthJson],
|
|
172
|
+
["whoami-json", providerWhoamiJson],
|
|
173
|
+
["whoami-text", providerWhoamiText],
|
|
174
|
+
["cache-md", providerCacheMd],
|
|
175
|
+
];
|
|
176
|
+
/** 兜底出口不进记忆:否则 e10-login 偶发不可用一次,就会把"用镜像"永久设为首选、次次告警 */
|
|
177
|
+
const AUTH_FALLBACK_PROVIDERS = new Set(["cache-md"]);
|
|
178
|
+
|
|
179
|
+
function readAuthModeMemo() {
|
|
180
|
+
try {
|
|
181
|
+
const s = fs.readFileSync(AUTH_MODE_FILE, "utf-8").trim();
|
|
182
|
+
return AUTH_PROVIDERS.some(([n]) => n === s) ? s : null;
|
|
183
|
+
} catch { return null; }
|
|
184
|
+
}
|
|
185
|
+
function writeAuthModeMemo(name) {
|
|
186
|
+
try { fs.mkdirSync(path.dirname(AUTH_MODE_FILE), { recursive: true }); fs.writeFileSync(AUTH_MODE_FILE, name, "utf-8"); } catch {}
|
|
105
187
|
}
|
|
106
|
-
|
|
188
|
+
|
|
189
|
+
/** 凭据取自 e10-login 时,顺手把明文镜像刷新到最新(保持镜像可用;镜像不参与正常取数) */
|
|
190
|
+
function syncCacheMd(auth) {
|
|
107
191
|
try {
|
|
108
192
|
const p = path.join(os.homedir(), ".workbuddy", "e10-login-cache.md");
|
|
109
193
|
if (!fs.existsSync(p) || !/🔐 登录信息缓存/.test(fs.readFileSync(p, "utf-8"))) return; // 非标准缓存文件不动它
|
|
@@ -117,16 +201,47 @@ function syncCacheMd(auth) { // 读到权威源后自动回写明文镜像(内
|
|
|
117
201
|
txt = n;
|
|
118
202
|
};
|
|
119
203
|
rep("baseUrl", auth.baseUrl); rep("ETEAMSID", auth.eteamsid); rep("完整 Cookie 串", auth.cookie); rep("userId", auth.userId || ""); rep("tenantKey", auth.tenantKey || "");
|
|
120
|
-
rep("缓存记录时间", ts + "(e10-data
|
|
204
|
+
rep("缓存记录时间", ts + "(e10-data 自动同步,凭据来源:" + (auth.source || "") + ")");
|
|
121
205
|
if (changed) fs.writeFileSync(p, txt, "utf-8");
|
|
122
206
|
} catch {}
|
|
123
207
|
}
|
|
208
|
+
|
|
209
|
+
/** 取凭据:按优先级依次尝试各出口;命中的模式会被记住(下次优先尝试它,省一次进程调用) */
|
|
124
210
|
function loadAuth() {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
211
|
+
const { profile, file } = authFilePath();
|
|
212
|
+
const override = (process.env.E10_DATA_AUTH_MODE || "").trim();
|
|
213
|
+
const memo = override || (process.env.E10_DATA_AUTH_PROBE ? null : readAuthModeMemo());
|
|
214
|
+
const order = memo
|
|
215
|
+
? AUTH_PROVIDERS.filter(([n]) => n === memo).concat(AUTH_PROVIDERS.filter(([n]) => n !== memo))
|
|
216
|
+
: AUTH_PROVIDERS;
|
|
217
|
+
|
|
218
|
+
const diag = [];
|
|
219
|
+
for (const [name, fn] of order) {
|
|
220
|
+
let got = null;
|
|
221
|
+
lastLoginDiag = "";
|
|
222
|
+
try { got = fn(); } catch (e) { diag.push(name + ": " + ((e && e.message) || e)); }
|
|
223
|
+
if (got) {
|
|
224
|
+
if (name !== memo && !AUTH_FALLBACK_PROVIDERS.has(name)) writeAuthModeMemo(name);
|
|
225
|
+
got.profile = profile;
|
|
226
|
+
if (name === "cache-md") {
|
|
227
|
+
if (!process.env.E10_DATA_QUIET) {
|
|
228
|
+
console.error("[e10-data][warn] 凭据来源=" + got.source + ";正常应由 e10-login 提供凭据,请检查 `e10-login whoami` 是否能输出 cookies。");
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
syncCacheMd(got); // 凭据来自 e10-login → 顺便把镜像刷新到最新
|
|
232
|
+
}
|
|
233
|
+
return got;
|
|
234
|
+
}
|
|
235
|
+
if (lastLoginDiag) diag.push(name + ": " + lastLoginDiag);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
throw new Error(
|
|
239
|
+
"未取得登录凭据(解密由 e10-login 负责,本 CLI 不参与)。请依次检查:\n" +
|
|
240
|
+
" ① e10-login 是否可用:`e10-login --version`(缺失 → `npm i -g e10-login`);`e10-login whoami` 应能输出 cookies\n" +
|
|
241
|
+
" ② 是否已登录 / 登录态是否失效:`e10-login oidc` 重新登录(当前 profile: " + profile + ")\n" +
|
|
242
|
+
" ③ auth 文件:" + (fs.existsSync(file) ? "存在" : "不存在") + "(" + file + ")—— 其加解密由 e10-login 负责\n" +
|
|
243
|
+
(diag.length ? " 探测诊断:" + diag.join(" | ") + "\n" : "")
|
|
244
|
+
);
|
|
130
245
|
}
|
|
131
246
|
const auth = loadAuth();
|
|
132
247
|
const BASE = auth.baseUrl.replace(/\/+$/, "");
|
|
@@ -135,7 +250,11 @@ const USER = { id: auth.userId || "", tenantKey: auth.tenantKey || "" };
|
|
|
135
250
|
// ---------------- HTTP ----------------
|
|
136
251
|
function checkAuthFail(resp, text, j) {
|
|
137
252
|
if (resp.status === 401 || (j && String(j.code) === "-1" && /token|登录|失效/i.test(String(j.msg || "")))) {
|
|
138
|
-
|
|
253
|
+
const from = (auth && auth.source) || "未知";
|
|
254
|
+
const hint = /镜像/.test(from)
|
|
255
|
+
? "当前用的是明文镜像兜底凭据(可能已过期),请先确认 `e10-login whoami` 能正常导出 cookies,或执行 `e10-login oidc` 重新登录"
|
|
256
|
+
: "凭据由 e10-login 提供,请执行 `e10-login oidc` 重新登录";
|
|
257
|
+
throw new Error("登录态失效(" + (j.msg || resp.status) + "):凭据来源=" + from + ";" + hint);
|
|
139
258
|
}
|
|
140
259
|
}
|
|
141
260
|
async function postJson(endpoint, bodyObj, extraHeaders = {}) {
|
|
@@ -377,17 +496,66 @@ function parseOrder(val) {
|
|
|
377
496
|
return { fieldId: f, fieldName: f, objId: "", isAsc: (dir || "asc").toLowerCase() !== "desc", mainField: true };
|
|
378
497
|
});
|
|
379
498
|
}
|
|
499
|
+
// ---------------- ds appId 自动解析(v0.1.12)----------------
|
|
500
|
+
// 背景:ChatBI 数据源取数接口(getListDatas / getChartDatas / getStatDatas / getCountData)
|
|
501
|
+
// 对部分 EB 应用(独立部署)的数据集必须携带 queryDto.appId,否则后端一律返回 code=500。
|
|
502
|
+
// 解析顺序(无缓存,每次实时查):--app-id <id> 显式 > chat_bi_eb_config_map(dw 权限配置表,按 obj_id,主来源)
|
|
503
|
+
// > ebdf_obj(ec_edcapp,按 id,兜底) > 不带 appId 发送(兼容旧行为)。
|
|
504
|
+
// 关闭自动解析:--no-app-id。
|
|
505
|
+
const APPID_SOURCES = [
|
|
506
|
+
{ confKey: "CONN_DW", model: "dw", schema: "dw", table: "chat_bi_eb_config_map", idField: "obj_id" },
|
|
507
|
+
{ confKey: "CONN_EC_EDCAPP", model: "ebuilder", schema: "ec_edcapp", table: "ebdf_obj", idField: "id" },
|
|
508
|
+
];
|
|
509
|
+
function dsFieldsForm(gid, tid, st) {
|
|
510
|
+
return { groupId: gid, sourceId: tid, sourceType: st, detailFieldsGroup: "true", enableMerge: "true", needTableName: "true", params: "[]" };
|
|
511
|
+
}
|
|
512
|
+
async function appIdByDsl(src, tid) {
|
|
513
|
+
const conn = await confGetData("conn", src.confKey, true);
|
|
514
|
+
const connId = (conn && conn.confValue) ? String(conn.confValue) : "";
|
|
515
|
+
if (!connId) return "";
|
|
516
|
+
const dslQuery = {
|
|
517
|
+
select: { columns: [{ field: "t1.app_id", alias: "app_id" }] },
|
|
518
|
+
from: { table: src.table, alias: "t1" },
|
|
519
|
+
where: { logic: "AND", conditions: [{ field: "t1." + src.idField, op: "=", value: String(tid) }, { field: "t1.delete_type", op: "=", value: 0 }] },
|
|
520
|
+
limit: { size: 1, offset: 0 },
|
|
521
|
+
};
|
|
522
|
+
const r = await postJson("/api/dw/dsl/executeQueryDsl", { connId, model: src.model, schema: src.schema, tableId: "", dslQuery });
|
|
523
|
+
const rows = Array.isArray(r && r.data) ? r.data : [];
|
|
524
|
+
return (rows[0] && rows[0].app_id) ? String(rows[0].app_id) : "";
|
|
525
|
+
}
|
|
526
|
+
async function resolveDsAppId(tid) {
|
|
527
|
+
for (const src of APPID_SOURCES) {
|
|
528
|
+
let appId = "";
|
|
529
|
+
try { appId = await appIdByDsl(src, tid); } catch { appId = ""; }
|
|
530
|
+
if (appId) return appId;
|
|
531
|
+
}
|
|
532
|
+
return "";
|
|
533
|
+
}
|
|
534
|
+
// ds 取数统一入口:--app-id <id> 显式 / --app-id auto 或省略=自动解析 / --no-app-id 跳过
|
|
535
|
+
async function dsDto(args, tid, gid, pageNo, pageSize, queryType) {
|
|
536
|
+
const dto = baseDto(tid, gid, pageNo, pageSize, queryType);
|
|
537
|
+
const explicit = args["app-id"];
|
|
538
|
+
if (typeof explicit === "string" && explicit !== "" && explicit !== "auto") {
|
|
539
|
+
dto.appId = explicit;
|
|
540
|
+
} else if (!args["no-app-id"]) {
|
|
541
|
+
const appId = await resolveDsAppId(tid);
|
|
542
|
+
if (appId) dto.appId = String(appId);
|
|
543
|
+
}
|
|
544
|
+
return dto;
|
|
545
|
+
}
|
|
380
546
|
async function cmdTables(args, json) {
|
|
381
547
|
const gid = need("group", args);
|
|
382
548
|
const form = { name: args.name || "", pageNo: args.page || "1", pageSize: args.size || "100", groupId: gid, sourceType: args["source-type"] || "LOGIC", dataUsage: "" };
|
|
549
|
+
if (typeof args["app-id"] === "string" && args["app-id"] !== "" && args["app-id"] !== "auto") form.appId = args["app-id"];
|
|
383
550
|
const r = await postMultipart("/api/ebuilder/common/ds/listByPage", form);
|
|
384
551
|
if (json) return r;
|
|
385
552
|
const recs = ((r.data || {}).records) || [];
|
|
386
|
-
printHuman("分组表清单 (total=" + ((r.data || {}).total) + ")", recs, ["id", "name", "tableText", "detailTable", "usage"]);
|
|
553
|
+
printHuman("分组表清单 (total=" + ((r.data || {}).total) + ")", recs, ["id", "name", "appId", "tableText", "detailTable", "usage"]);
|
|
387
554
|
}
|
|
388
555
|
async function cmdFields(args, json) {
|
|
389
556
|
const gid = need("group", args), tid = need("table", args);
|
|
390
|
-
const form =
|
|
557
|
+
const form = dsFieldsForm(gid, tid, args["source-type"] || "LOGIC");
|
|
558
|
+
if (typeof args["app-id"] === "string" && args["app-id"] !== "" && args["app-id"] !== "auto") form.appId = args["app-id"];
|
|
391
559
|
const r = await postMultipart("/api/ebuilder/common/ds/fields", form);
|
|
392
560
|
if (json) return r;
|
|
393
561
|
for (const d of (r.data || [])) {
|
|
@@ -398,7 +566,7 @@ async function cmdFields(args, json) {
|
|
|
398
566
|
async function cmdList(args, json) {
|
|
399
567
|
const gid = need("group", args), tid = need("table", args), fld = need("fields", args);
|
|
400
568
|
const fields = fld.split(",").map((f) => ({ fieldId: f, fieldName: f, objId: String(tid), mainField: true }));
|
|
401
|
-
const dto =
|
|
569
|
+
const dto = await dsDto(args, tid, gid, parseInt(args.page || "1", 10), parseInt(args.size || "20", 10), "getListData");
|
|
402
570
|
dto.queryFields = fields;
|
|
403
571
|
dto.orderFields = parseOrder(args.order);
|
|
404
572
|
const ct = parseWhere(args.where); if (ct) dto.conditionTrees = ct;
|
|
@@ -411,7 +579,7 @@ async function cmdList(args, json) {
|
|
|
411
579
|
}
|
|
412
580
|
async function cmdChart(args, json) {
|
|
413
581
|
const gid = need("group", args), tid = need("table", args), gb = need("group-by", args);
|
|
414
|
-
const dto =
|
|
582
|
+
const dto = await dsDto(args, tid, gid, parseInt(args.page || "1", 10), parseInt(args.size || "100", 10), "getChartData");
|
|
415
583
|
dto.groupFields = gb.split(",").map((f) => ({ fieldId: f, fieldName: f, objId: String(tid), mainField: true }));
|
|
416
584
|
dto.groupStatFields = parseStat(need("stat", args)).map((s) => ({ ...s, objId: String(tid) }));
|
|
417
585
|
const ct = parseWhere(args.where); if (ct) dto.conditionTrees = ct;
|
|
@@ -421,7 +589,7 @@ async function cmdChart(args, json) {
|
|
|
421
589
|
}
|
|
422
590
|
async function cmdStat(args, json) {
|
|
423
591
|
const gid = need("group", args), tid = need("table", args);
|
|
424
|
-
const dto =
|
|
592
|
+
const dto = await dsDto(args, tid, gid, 1, 10, "getStatData");
|
|
425
593
|
dto.statFields = parseStat(need("stat", args)).map((s) => ({ ...s, objId: String(tid) }));
|
|
426
594
|
const ct = parseWhere(args.where); if (ct) dto.conditionTrees = ct;
|
|
427
595
|
const r = await postMultipart("/api/dw/newetl/chatBi/ds/getStatDatas", { sourceType: args["source-type"] || "LOGIC", queryDto: dto });
|
|
@@ -432,7 +600,7 @@ async function cmdStat(args, json) {
|
|
|
432
600
|
}
|
|
433
601
|
async function cmdCount(args, json) {
|
|
434
602
|
const gid = need("group", args), tid = need("table", args);
|
|
435
|
-
const dto =
|
|
603
|
+
const dto = await dsDto(args, tid, gid, 1, 10, "getCountData");
|
|
436
604
|
const ct = parseWhere(args.where); if (ct) dto.conditionTrees = ct;
|
|
437
605
|
const r = await postMultipart("/api/dw/newetl/chatBi/ds/getCountData", { sourceType: args["source-type"] || "LOGIC", queryDto: dto });
|
|
438
606
|
if (json) return r;
|
|
@@ -1242,6 +1410,9 @@ ds 数据源(业务模块)取数与元数据
|
|
|
1242
1410
|
ds stat --group <gid> --table <tid> --stat '[...]' [--where ...]
|
|
1243
1411
|
ds count --group <gid> --table <tid> [--where ...]
|
|
1244
1412
|
(ds 通用 --source-type LOGIC|ETEAMS,默认 LOGIC)
|
|
1413
|
+
(ds 取数 list|chart|stat|count 的 appId:默认自动解析并带上——顺序 chat_bi_eb_config_map(dw 权限配置表,按 obj_id)
|
|
1414
|
+
→ ebdf_obj(ec_edcapp,按 id),每次实时查、不缓存;也可 --app-id <id> 显式指定、--app-id auto 强制自动、
|
|
1415
|
+
--no-app-id 完全跳过自动解析(最快,适用于无需 appId 的数据集);ds tables/fields 也接受 --app-id)
|
|
1245
1416
|
--where 过滤条件(v0.1.8 起支持树形 OR,递归):
|
|
1246
1417
|
数组 = AND 根: [{"fieldId":"f1","conditionType":"Date","compareType":"range","conditionValue":"2026-09~2026-10"},{"fieldId":"f2",...}]
|
|
1247
1418
|
对象 = 完整树: {"relationship":"0","conditionList":[{"fieldId":"f1",...},{"fieldId":"f2",...}]} # "0"=OR、"1"=AND,缺省 AND
|
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 专家配套):DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf,支持 confGroup 分组)
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"description": "泛微 E10 数据接口统一 CLI(data-assistant 专家配套):DSL 查询/数据源/文档/词库/关联字段翻译/环境参数配置中心(conf,支持 confGroup 分组)/专家包版本管理;ds 取数(list/chart/stat/count)自动解析并携带 queryDto.appId(v0.1.12,chat_bi_eb_config_map → ebdf_obj;--app-id 显式/--no-app-id 跳过);认证加解密全部交由 weaver-e10-login(本 CLI 不再自行解密,通过 e10-login 命令出口获取凭据)",
|
|
5
5
|
"author": "e10-dw",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|