@microi.net/cli 5.3.7 → 5.3.9
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/.codebuddy-plugin/marketplace.json +2 -2
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/assets/build-meta.json +4 -4
- package/cordis.patch.yml +1 -1
- package/package.json +1 -1
- package/scripts/mcp-server.js +3 -3
- package/scripts/microi-skills.meta.json +201 -201
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +33 -33
- package/skills/microi-deployment/SKILL.md +17 -3
- package/skills/microi-form-engine/SKILL.md +5 -2
- package/skills/microi-form-engine/references/component-catalog.md +2 -1
- package/skills/microi-form-engine/references/data-source-events.md +7 -0
- package/skills/microi-frontend-sdk/SKILL.md +5 -4
- package/skills/microi-uniapp-frontend/SKILL.md +3 -1
- package/skills/microi.v8.js +86 -86
- package/skills/ocr-engine/SKILL.md +113 -113
- package/skills/translate-engine/SKILL.md +140 -140
- package/skills/v8-file-upload/SKILL.md +11 -4
- package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +9 -2
- package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +1 -1
- package/skills/v8-security/SKILL.md +10 -4
- package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +123 -123
- package/skills/v8-utilities/SKILL.md +2 -1
- package/skills/v8-utilities/references/server-api-index.md +40 -10
- package/skills/workspace-conventions/SKILL.md +194 -194
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"generatedAt": "2026-08-
|
|
3
|
+
"generatedAt": "2026-08-26T19:09:39.599Z",
|
|
4
4
|
"policy": {
|
|
5
5
|
"maxEntryLines": 285,
|
|
6
6
|
"referenceBudgetLines": 245,
|
|
@@ -407,11 +407,11 @@
|
|
|
407
407
|
]
|
|
408
408
|
},
|
|
409
409
|
"microi-frontend-sdk": {
|
|
410
|
-
"originalSha256": "
|
|
411
|
-
"originalLines":
|
|
410
|
+
"originalSha256": "27802e252aa4c9d85e4e10c70baa9d05c7207dcf943c5305c24a586b17e0fac7",
|
|
411
|
+
"originalLines": 318,
|
|
412
412
|
"prefixSha256": "5ee94a9174a22577011c64f29a93f6983c8450d471d26f12fa884d7c4b3a879a",
|
|
413
413
|
"prefixBytes": 895,
|
|
414
|
-
"entryLines":
|
|
414
|
+
"entryLines": 191,
|
|
415
415
|
"chunks": [
|
|
416
416
|
{
|
|
417
417
|
"id": "microi-frontend-sdk-000",
|
|
@@ -433,7 +433,7 @@
|
|
|
433
433
|
"id": "microi-frontend-sdk-002",
|
|
434
434
|
"ordinal": 2,
|
|
435
435
|
"destination": "microi-frontend-sdk/SKILL.md",
|
|
436
|
-
"sha256": "
|
|
436
|
+
"sha256": "b71205548e4701f8bf53e2bed3da914da4ca0f49e0cce952a1c938fc77218ed8",
|
|
437
437
|
"lines": 48,
|
|
438
438
|
"heading": "登录与验证码封装"
|
|
439
439
|
},
|
|
@@ -449,8 +449,8 @@
|
|
|
449
449
|
"id": "microi-frontend-sdk-004",
|
|
450
450
|
"ordinal": 5,
|
|
451
451
|
"destination": "microi-frontend-sdk/SKILL.md",
|
|
452
|
-
"sha256": "
|
|
453
|
-
"lines":
|
|
452
|
+
"sha256": "1ecb73f5b1246c7157d782b62cba59850389858baeefdc5351a255d4d04b11b6",
|
|
453
|
+
"lines": 18,
|
|
454
454
|
"heading": "上传规则"
|
|
455
455
|
},
|
|
456
456
|
{
|
|
@@ -942,11 +942,11 @@
|
|
|
942
942
|
]
|
|
943
943
|
},
|
|
944
944
|
"microi-uniapp-frontend": {
|
|
945
|
-
"originalSha256": "
|
|
946
|
-
"originalLines":
|
|
947
|
-
"prefixSha256": "
|
|
948
|
-
"prefixBytes":
|
|
949
|
-
"entryLines":
|
|
945
|
+
"originalSha256": "a9e7ebb927e8be11a7d4d273f83b3a6f200835c2361daa944ec200d0580c3e2b",
|
|
946
|
+
"originalLines": 501,
|
|
947
|
+
"prefixSha256": "05053116f81ef9286c72590cd6c5670e3b38c594bf76fc5d41a32675f391d9a5",
|
|
948
|
+
"prefixBytes": 2988,
|
|
949
|
+
"entryLines": 192,
|
|
950
950
|
"chunks": [
|
|
951
951
|
{
|
|
952
952
|
"id": "microi-uniapp-frontend-000",
|
|
@@ -976,7 +976,7 @@
|
|
|
976
976
|
"id": "microi-uniapp-frontend-003",
|
|
977
977
|
"ordinal": 3,
|
|
978
978
|
"destination": "microi-uniapp-frontend/SKILL.md",
|
|
979
|
-
"sha256": "
|
|
979
|
+
"sha256": "5cedd05cb29746019a92db56a21123718fde23d5d9df4b8064dfc260bc89ee05",
|
|
980
980
|
"lines": 17,
|
|
981
981
|
"heading": "登录页与手机号快捷登录"
|
|
982
982
|
},
|
|
@@ -2221,11 +2221,11 @@
|
|
|
2221
2221
|
]
|
|
2222
2222
|
},
|
|
2223
2223
|
"v8-file-upload": {
|
|
2224
|
-
"originalSha256": "
|
|
2225
|
-
"originalLines":
|
|
2226
|
-
"prefixSha256": "
|
|
2227
|
-
"prefixBytes":
|
|
2228
|
-
"entryLines":
|
|
2224
|
+
"originalSha256": "69f7195bfeb618f0020da92535f47c8eb183d431cf16492a12f6f61dbcc7a851",
|
|
2225
|
+
"originalLines": 571,
|
|
2226
|
+
"prefixSha256": "796e8308ae21c36dde1b9268ae71ccb8c2afe9f393d7f9865b6648d2f7f0f22e",
|
|
2227
|
+
"prefixBytes": 8923,
|
|
2228
|
+
"entryLines": 226,
|
|
2229
2229
|
"chunks": [
|
|
2230
2230
|
{
|
|
2231
2231
|
"id": "v8-file-upload-000",
|
|
@@ -2247,7 +2247,7 @@
|
|
|
2247
2247
|
"id": "v8-file-upload-002",
|
|
2248
2248
|
"ordinal": 2,
|
|
2249
2249
|
"destination": "v8-file-upload/SKILL.md",
|
|
2250
|
-
"sha256": "
|
|
2250
|
+
"sha256": "ed7e727c51bd2416ae1f38cf3c4bd09491e616df08473a610372e0ca648e1298",
|
|
2251
2251
|
"lines": 101,
|
|
2252
2252
|
"heading": "接收前端上传的文件"
|
|
2253
2253
|
},
|
|
@@ -2271,8 +2271,8 @@
|
|
|
2271
2271
|
"id": "v8-file-upload-005",
|
|
2272
2272
|
"ordinal": 3,
|
|
2273
2273
|
"destination": "v8-file-upload/references/progressive-01-公有桶-vs-私有桶.md",
|
|
2274
|
-
"sha256": "
|
|
2275
|
-
"lines":
|
|
2274
|
+
"sha256": "b09a96f25aef20eb8cbb204bd785cce370dcd2c9a28563c6f6d720fbdaa5218f",
|
|
2275
|
+
"lines": 150,
|
|
2276
2276
|
"heading": "公有桶 vs 私有桶"
|
|
2277
2277
|
},
|
|
2278
2278
|
{
|
|
@@ -2295,7 +2295,7 @@
|
|
|
2295
2295
|
"id": "v8-file-upload-008",
|
|
2296
2296
|
"ordinal": 8,
|
|
2297
2297
|
"destination": "v8-file-upload/references/progressive-02-office-文件在线编辑版本号规则.md",
|
|
2298
|
-
"sha256": "
|
|
2298
|
+
"sha256": "5e3b59486745d3c54bd8ad25c43cbe2396e6d614394340b93d93794b6df56292",
|
|
2299
2299
|
"lines": 36,
|
|
2300
2300
|
"heading": "Office 文件在线编辑版本号规则"
|
|
2301
2301
|
},
|
|
@@ -2786,11 +2786,11 @@
|
|
|
2786
2786
|
]
|
|
2787
2787
|
},
|
|
2788
2788
|
"v8-security": {
|
|
2789
|
-
"originalSha256": "
|
|
2790
|
-
"originalLines":
|
|
2789
|
+
"originalSha256": "f3624718dfa642f4c3ab5d48a33022014aa467af1be756fdbc78c4bae2231d1c",
|
|
2790
|
+
"originalLines": 503,
|
|
2791
2791
|
"prefixSha256": "c3b2ae10a39461ee69fda3c7e994d072ca6d8c9dc6f2338742bc8f07e0dcffd0",
|
|
2792
2792
|
"prefixBytes": 1648,
|
|
2793
|
-
"entryLines":
|
|
2793
|
+
"entryLines": 190,
|
|
2794
2794
|
"chunks": [
|
|
2795
2795
|
{
|
|
2796
2796
|
"id": "v8-security-000",
|
|
@@ -2804,8 +2804,8 @@
|
|
|
2804
2804
|
"id": "v8-security-001",
|
|
2805
2805
|
"ordinal": 1,
|
|
2806
2806
|
"destination": "v8-security/SKILL.md",
|
|
2807
|
-
"sha256": "
|
|
2808
|
-
"lines":
|
|
2807
|
+
"sha256": "aad35b2c3e1876d98ead7e604307bcb7d39fe8c480d72f102ae956972580a35b",
|
|
2808
|
+
"lines": 19,
|
|
2809
2809
|
"heading": "0.5 接口引擎配置安全"
|
|
2810
2810
|
},
|
|
2811
2811
|
{
|
|
@@ -2860,7 +2860,7 @@
|
|
|
2860
2860
|
"id": "v8-security-008",
|
|
2861
2861
|
"ordinal": 8,
|
|
2862
2862
|
"destination": "v8-security/references/progressive-02-7-日志记录.md",
|
|
2863
|
-
"sha256": "
|
|
2863
|
+
"sha256": "6e0b7de3cd72cc030c93566c03ddf2191af6163d378e5ec8a40828eee9873477",
|
|
2864
2864
|
"lines": 40,
|
|
2865
2865
|
"heading": "7. 日志记录"
|
|
2866
2866
|
},
|
|
@@ -2868,7 +2868,7 @@
|
|
|
2868
2868
|
"id": "v8-security-009",
|
|
2869
2869
|
"ordinal": 9,
|
|
2870
2870
|
"destination": "v8-security/references/progressive-02-7-日志记录.md",
|
|
2871
|
-
"sha256": "
|
|
2871
|
+
"sha256": "b7a65ce2785a6a3d02dc1bc08c16c5ae03e38aa199d4bc204abe5978057f1ea1",
|
|
2872
2872
|
"lines": 20,
|
|
2873
2873
|
"heading": "8. 错误处理"
|
|
2874
2874
|
},
|
|
@@ -2876,7 +2876,7 @@
|
|
|
2876
2876
|
"id": "v8-security-010",
|
|
2877
2877
|
"ordinal": 10,
|
|
2878
2878
|
"destination": "v8-security/references/progressive-02-7-日志记录.md",
|
|
2879
|
-
"sha256": "
|
|
2879
|
+
"sha256": "4b68e2730cc6ac2ba8eeb4458c71e9ee157b9ac8054dcab364f364b2e0bd1c05",
|
|
2880
2880
|
"lines": 25,
|
|
2881
2881
|
"heading": "9. Token、终端会话与租户隔离"
|
|
2882
2882
|
},
|
|
@@ -2884,7 +2884,7 @@
|
|
|
2884
2884
|
"id": "v8-security-011",
|
|
2885
2885
|
"ordinal": 11,
|
|
2886
2886
|
"destination": "v8-security/references/progressive-02-7-日志记录.md",
|
|
2887
|
-
"sha256": "
|
|
2887
|
+
"sha256": "78053ef853bdffb4eaad90354204555d836d67b9a18f2a6c0ac74a0142df2794",
|
|
2888
2888
|
"lines": 11,
|
|
2889
2889
|
"heading": "10. Jint 运行时升级边界"
|
|
2890
2890
|
},
|
|
@@ -2892,7 +2892,7 @@
|
|
|
2892
2892
|
"id": "v8-security-012",
|
|
2893
2893
|
"ordinal": 12,
|
|
2894
2894
|
"destination": "v8-security/references/progressive-02-7-日志记录.md",
|
|
2895
|
-
"sha256": "
|
|
2895
|
+
"sha256": "f7ac9ee1702759fbf5b256ef7b7069ebce75cb845b59786d4eab928335001bf6",
|
|
2896
2896
|
"lines": 30,
|
|
2897
2897
|
"heading": "安全检查清单"
|
|
2898
2898
|
},
|
|
@@ -2900,7 +2900,7 @@
|
|
|
2900
2900
|
"id": "v8-security-013",
|
|
2901
2901
|
"ordinal": 13,
|
|
2902
2902
|
"destination": "v8-security/references/progressive-02-7-日志记录.md",
|
|
2903
|
-
"sha256": "
|
|
2903
|
+
"sha256": "809cd12c1b5fbba76a0b09ebbddf5d2e34189d26ee510c285389294c35b91176",
|
|
2904
2904
|
"lines": 22,
|
|
2905
2905
|
"heading": "浏览器访问密钥"
|
|
2906
2906
|
}
|
|
@@ -74,14 +74,21 @@ Microi API 安装时,`AppSettings` 与同名容器环境变量只允许以下
|
|
|
74
74
|
- 每次修改 `数据库、案例、文档、资料/install-microi.sh`,必须同时更新文件头版本和 `SCRIPT_VERSION`,两处完全一致。
|
|
75
75
|
- 固定格式为 `vYYYY-MM-DD HH:mm:ss`,使用 `Asia/Shanghai` 时间并精确到秒;禁止只写日期或沿用上一次修改时间。
|
|
76
76
|
- 验收同时断言两处版本一致、格式正确,并以脚本实际启动输出为准;官网静态地址尚未发布新文件时,不能把本地版本误报为客户已经可下载。
|
|
77
|
-
- 恢复旧数据库时,OCR
|
|
77
|
+
- 恢复旧数据库时,OCR、翻译等附加字段先出现不代表完整升级链已经成功。安装器必须先回读 `sys_config.ServerVersion` 达到本脚本最低版本并通过 API liveness/readiness,才能部署和配置附加能力;核心中间迁移失败或超时应失败关闭,禁止用部分字段就绪冒充整个平台升级成功,也禁止在升级事务仍执行时主动重启 API。
|
|
78
78
|
- Compose v2 生成文件不再写顶层 `version`;所有模板直接以 `services:` 开始,避免 `the attribute version is obsolete` 告警。
|
|
79
79
|
- LibreTranslate 属于一键安装默认组件:安装选择空输入按 `1` 处理,语言套餐空输入按基础套餐 `1` 处理;因此用户一路按 Enter 使用官方推荐组合。只有明确输入 `0` 才跳过,提示、端口预算、官网文档和静态回归测试必须一致。
|
|
80
|
-
- OCR 的 Upgrade29 与 LibreTranslate 的 Upgrade31
|
|
80
|
+
- OCR 的 Upgrade29 与 LibreTranslate 的 Upgrade31 字段门禁只在对应附加服务部署检查通过后回读,每秒一次、最多 15 秒;首轮成功就继续,超时快速关闭对应能力并提示镜像/迁移版本,不得退回 5 分钟空等,也不得直接创建字段绕过平台升级。附加能力门禁失败不得中断已通过完整升级链和 readiness 的核心平台。
|
|
81
81
|
- 官方 API/Web 使用浮动 `latest` 时,生成的 Compose 必须设置 `pull_policy: always` 或在启动前显式拉取并核对,不能因宿主机已有同名镜像就复用旧版本;测试专用本机镜像覆盖必须明确使用 `never`,避免误访问或覆盖远端镜像。
|
|
82
|
-
-
|
|
82
|
+
- 端口、密码和数据目录全部生成后,安装器必须注册统一的失败收尾:数据库、缓存、存储、API/Web、完整升级链或 API liveness/readiness 等核心后段错误保留原始非零退出码,并输出标题为“安装未完成”的恢复汇总,列出已生成端口、凭据、目录、失败阶段和全部容器状态。OCR/LibreTranslate 的网络、镜像、容器或 SaaS 配置失败只记录附加能力警告、保持对应能力未启用并继续核心安装。两种结果都不得把 `Running` 当 readiness,也不得绕过 Upgrade29/Upgrade31 或完整升级链门禁。
|
|
83
83
|
- 从客户/旧库恢复安装时,安装器只按 `OsClient + OsClientType + OsClientNetwork + IsEnable + IsDeleted` 处理目标主租户:0 条时幂等创建最小可运行记录,1 条时原位复用,超过 1 条失败关闭。禁止把所有活动 `sys_osclients` 批量改成输入的 OsClient;新记录不得落库 `DbConn/DbReadConn/DbMongoConnection` 或 Redis 主机、端口、密码,这些继续由十项编排启动配置提供。MinIO、OCR 等安装值随后只更新这个精确三元组并回读唯一性。
|
|
84
84
|
|
|
85
|
+
### 复盘:普通帐号运行安装器直到步骤 5 才因 `/microi/compose` 权限失败
|
|
86
|
+
|
|
87
|
+
- 触发场景:普通帐号已经加入 Docker 组,前四步的探测和交互看似正常;脚本在开放防火墙后直接执行 `mkdir -p /microi/compose`,才报 `Permission denied`。命令替换中的数据目录创建错误还可能被旧 Bash 的 `set -e` 语义掩盖,使成功提示先于真实失败出现。
|
|
88
|
+
- 根因:安装器把 `sudo` 零散放在软件包、防火墙和 systemd 命令前,却没有在真实安装或修复入口统一取得 root 身份;同时假设精简系统一定安装 `sudo`,普通用户与“root 但无 sudo”两种环境都没有明确的早期门禁。
|
|
89
|
+
- 通用规则:会写 `/microi`、`/home`、防火墙、systemd/cgroup 或 Docker 宿主状态的一键安装/修复,必须在步骤 1 和任何交互、凭据生成、宿主机写入前完成一次 root 门禁。普通帐号有 `sudo` 时重新执行同一绝对脚本并保留参数;无 `sudo` 或验证失败时零副作用停止并给出 `su -` 恢复指引。已经是 root 时不得要求系统额外安装 `sudo`;目录创建仍要显式检查只读文件系统、空间和权限错误。
|
|
90
|
+
- 自动化检查:提供只执行权限门禁的无副作用入口;在本地 Linux 容器分别覆盖 root 且无 sudo、普通帐号且无 sudo、普通帐号通过 sudo 提权三种场景,断言成功路径最终 `uid=0`、失败路径发生在步骤 1 之前且不创建 `/microi`。源码契约同时锁定正常安装与 `--repair-app` 均调用同一门禁,并执行 `bash -n`。
|
|
91
|
+
|
|
85
92
|
## 多节点与滚动发布
|
|
86
93
|
|
|
87
94
|
后端默认按多个 API/Worker 节点设计:
|
|
@@ -153,6 +160,13 @@ Microi API 安装时,`AppSettings` 与同名容器环境变量只允许以下
|
|
|
153
160
|
- 通用规则:发布冒烟必须启动最终混淆/签名后的产物,确认本次进程仍存活,再以 `/api/Diagnostics/liveness` HTTP 200 判定启动成功;使用专用空闲端口,成功、超时、异常和中断路径都要在有限时间内结束本次进程并清理 publish 内的临时 logs/PDB。版本号已经更新但尚未提交时,重跑默认继续工作区当前版本,不能再次自动递增;当前版本进入 Git HEAD 后才计算下一版本。
|
|
154
161
|
- 自动化检查:构造不再输出旧中文文案但 liveness 返回 200 的版本,断言冒烟成功;另覆盖端口占用、进程提前退出和 liveness 超时,断言脚本失败原因准确、进程无残留、`publish/logs` 不存在。再把工作区版本设为高于 HEAD,验证重跑默认版本保持不变。
|
|
155
162
|
|
|
163
|
+
### 复盘:旧租户启动闭包把应用包展示字段当成物理列并反复重启
|
|
164
|
+
|
|
165
|
+
- 触发场景:升级后的节点发现平台运行时接口缺失,准备从内置官方应用包自愈;旧包把接口名称导出为 `Name`,目标 `sys_apiengine` 的真实字段却是 `ApiName`,直接拼接包对象后出现 `Unknown column 'Name' in 'field list'`。修完别名后,另一批旧库又因 `UserId NOT NULL` 且无默认值、而少数新接口包漏出审计字段,出现 `Field 'UserId' doesn't have a default value`。启动门禁失败会使 Docker 的 restart policy 不断重启同一进程。
|
|
166
|
+
- 根因:启动自愈把“应用包传输对象”误当成“数据库物理行”,既没有规范化历史别名,也没有按目标租户的真实表结构过滤;同时只检查“列是否存在”,却没有覆盖旧结构中仍为必填且无默认值的审计列。不同年代租户会依次暴露 unknown-column 或 required-column 错误。
|
|
167
|
+
- 通用规则:任何在 FormEngine 可用前直接写核心物理表的可信自愈,都必须先规范化已知历史别名,再应用固定实体字段白名单,并与目标租户实时回读的物理列取交集;SQL 只使用经过双重筛选的固定标识符和参数值。官方包本身必须携带完整审计字段;无登录上下文的启动门禁还必须为历史必填审计列提供稳定的系统身份兜底,并为 `IsDeleted/IsEnable/Lock` 等开关及 `ApiRole/Files` 等集合字段补齐语义明确的插入默认值,这些默认值不得参与授权。必需业务列缺失时给出明确物理升级错误,写入后按稳定 Key 强回读,禁止把包级元数据直接转换成列名。
|
|
168
|
+
- 自动化检查:使用包含旧 `Name`、未来包级字段、新版本可选列及缺失 `UserId/UserName/Lock` 的官方包样例,分别对完整结构与精简旧结构断言 `Name -> ApiName`、未知字段被丢弃、必需字段保留、审计字段和传统必填列获得稳定非空值;同时扫描全部启动闭包包,保证 `SysApiEngines` 只输出 `ApiName` 且审计字段及传统必填开关完整,并锁定新增接口生成器输出的集合字段,最后执行真实启动日志门禁回归。
|
|
169
|
+
|
|
156
170
|
## 禁止事项
|
|
157
171
|
|
|
158
172
|
- 不把数据库/Redis/MinIO 密码写入仓库、日志、命令回显或最终答复。
|
|
@@ -57,8 +57,11 @@ Config/Data、菜单查询列与缓存保持一致。
|
|
|
57
57
|
图片默认压缩到约 500 KB、最长边 1920 px。
|
|
58
58
|
- 私有富文本只能持久化稳定对象标识,禁止把 HDFS 签名 URL、审计代理 Ticket 或 DiyToken 写入
|
|
59
59
|
HTML。每次查看/编辑按当前菜单、表、记录和字段权限换取新短效地址;外部匿名页面没有此权限
|
|
60
|
-
上下文,因此不能把私有 RichText
|
|
61
|
-
|
|
60
|
+
上下文,因此不能把私有 RichText 当作公开正文。普通用户通过当前表单新增/编辑授权后,后端
|
|
61
|
+
必须回读 `diy_field.Config.RichText.Limit` 决定公私桶;只改请求 `Limit=false` 不能绕过字段配置。
|
|
62
|
+
- 图片、文件和富文本上传必须携带 `FormEngineKey + FieldId + SysMenuId`,编辑记录再带
|
|
63
|
+
`FormDataId`,TableChild 再带父子授权上下文。后端不得按用户等级统一覆盖字段的“禁止匿名访问”;
|
|
64
|
+
无法回查字段与动作权限的普通上传才安全降级为私有桶。
|
|
62
65
|
|
|
63
66
|
新建表/模块时,除非用户显式指定或表单达到极重阈值(约 36+ 业务字段、2+ 子表或同等
|
|
64
67
|
重型控件密度),默认保存 `diy_table.FormOpenType=Dialog` 与 `FormOpenWidth=80%`。
|
|
@@ -178,7 +178,7 @@ if (V8.LoadMode !== 'Design') {
|
|
|
178
178
|
}
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
- `Limit=true` 表示字段配置为私有桶,`false`
|
|
181
|
+
- `Limit=true` 表示字段配置为私有桶,`false` 表示配置为公有桶。普通用户有当前表单新增/编辑权限时也按此权威配置执行;运行时提交表、字段、菜单、记录上下文,后端回读 `diy_field.Config`,不得信任客户端 `Limit/Path`,也不得按用户等级统一强制私有。
|
|
182
182
|
- `Multiple=true` 时 `MaxCount` 必须是正整数;`MaxSize` 单位为 MB。`Preview` 未配置时按 `true` 压缩,普通列表、卡片、商品图不要关闭。
|
|
183
183
|
- `Crop.Enabled` 只决定运行时裁剪开关的初始状态;`false` 或缺失时用户仍可在紧凑上传面板主动开启。裁剪前原图始终进入 HDFS 私有桶,业务字段只保存展示图元数据。
|
|
184
184
|
- 图片和文件都把拖放提示、存储范围、单/多文件、最大数量、处理方式和最大体积合并到同一个紧凑面板;不要在字段外再生成重复说明卡片。
|
|
@@ -205,6 +205,7 @@ if (V8.LoadMode !== 'Design') {
|
|
|
205
205
|
```
|
|
206
206
|
|
|
207
207
|
- 公开公告、商品详情等匿名网页内容显式用 `Limit=false`;内部公告、合同说明等用 `true`。旧字段缺失时默认私有,不能由 AI 猜成公有。
|
|
208
|
+
- 普通用户经当前表单新增/编辑授权后可以发布公有 RichText;后端按字段权威配置选择桶。缺少 `FormEngineKey/FieldId` 等可验证上下文的普通上传仍保持私有,不能用裸 `Limit=false` 代替授权。
|
|
208
209
|
- `MaxSize` 单位 MB,`CompressMaxSize` 单位 KB。图片压缩开启时原图仍先进入 HDFS 私有桶,正文只引用展示图。
|
|
209
210
|
- 私有正文只保存 `/__microi_richtext_private__/...` 稳定标识;运行时携带当前菜单、表、记录和字段上下文换取短效代理 URL,禁止把临时 Token/Ticket 回写字段。
|
|
210
211
|
- 普通文件通过富文本工具栏附件入口插入 `a.href`;私有授权只接受真实 `img/video/source.src` 与 `a.href` 的精确路径,不接受正文文字、`data-*` 或脚本标签。
|
|
@@ -87,6 +87,13 @@ if (!V8.Form.Name) return { Code: 0, Msg: '名称不能为空' };
|
|
|
87
87
|
- 修改防抖编辑器/代码字段前必须刷新待保存值,避免最后一次输入未入库。
|
|
88
88
|
- `Data/Config` 修改后回读并刷新 schema 缓存。
|
|
89
89
|
|
|
90
|
+
### 历史字段 V8 双存储兼容
|
|
91
|
+
|
|
92
|
+
- `diy_field.V8Code` 是历史租户和官方应用包实际使用的物理列,按钮点击/字段值变更代码可能只存在于该列;`Config.V8Code` 是部分新版前端使用的镜像,不能假定它是唯一事实源。
|
|
93
|
+
- 后端 `DiyField` 实体、字段查询投影和 `DiyFieldParam` 必须保留 `V8Code`,否则数据库代码虽然仍在,设计器列表和单字段回读都会表现成“0 字”。
|
|
94
|
+
- 前端回读顺序固定为:先安全解码历史 Base64,再取“非空物理 `V8Code` 优先、否则 `Config.V8Code`”,最后同步两处。禁止用空镜像覆盖非空物理代码;保存和显式清空时则必须同步写入两处。
|
|
95
|
+
- 回归至少包含一个 `Component=Button`、根级 `V8Code` 非空而 `Config.V8Code` 为空的官方包字段,并验证列表回读、选中编辑、保存、刷新后代码仍完整。
|
|
96
|
+
|
|
90
97
|
## DevComponent 定制组件
|
|
91
98
|
|
|
92
99
|
定制组件至少处理:
|
|
@@ -71,7 +71,7 @@ export function createApp() {
|
|
|
71
71
|
`Microi.Client` 主后台运行时已内置前后端同构的 `V8.Http.Get/Post/Patch` 及对应 Response 方法;表单事件、按钮 V8 等宿主前端新代码必须优先使用 `V8.Http`,旧 `V8.Post/Get` 仅作兼容保留,其参数和兼容规则以 `v8-http-integration/SKILL.md` 为准。独立项目使用本 SDK、且不在主后台 V8 宿主中时,才使用 SDK 自身的小写 `V8.get/post`、`ApiEngine`、`FormEngine`;不要把它们与宿主旧版大写 `V8.Post/Get` 混为一谈,也不要假设浏览器可以绕过第三方接口的 CORS。
|
|
72
72
|
|
|
73
73
|
<!-- /microi-progressive:chunk -->
|
|
74
|
-
<!-- microi-progressive:chunk id=microi-frontend-sdk-002 sha256=
|
|
74
|
+
<!-- microi-progressive:chunk id=microi-frontend-sdk-002 sha256=b71205548e4701f8bf53e2bed3da914da4ca0f49e0cce952a1c938fc77218ed8 -->
|
|
75
75
|
## 登录与验证码封装
|
|
76
76
|
|
|
77
77
|
SDK 或项目请求模块必须提供登录所需的系统配置和验证码薄封装,不要让页面散落手写。
|
|
@@ -132,15 +132,16 @@ SDK 的 `buildHeaders` 必须集中处理所有请求头,不能让页面、业
|
|
|
132
132
|
- 验收时检查真实网络请求:不得出现 `osclient: demo, demo`、`Authorization: Bearer xxx, Bearer xxx` 这类逗号合并值。
|
|
133
133
|
|
|
134
134
|
<!-- /microi-progressive:chunk -->
|
|
135
|
-
<!-- microi-progressive:chunk id=microi-frontend-sdk-004 sha256=
|
|
135
|
+
<!-- microi-progressive:chunk id=microi-frontend-sdk-004 sha256=1ecb73f5b1246c7157d782b62cba59850389858baeefdc5351a255d4d04b11b6 -->
|
|
136
136
|
## 上传规则
|
|
137
137
|
|
|
138
138
|
`V8.uploadFile` 是 Microi 前端唯一允许的上传入口。SDK 实现必须:
|
|
139
139
|
|
|
140
140
|
- 使用 multipart 上传头。`uni.uploadFile` 或 `fetch(FormData)` 不得发送 `Content-Type: application/json`。
|
|
141
141
|
- 租户请求头只发送一个键:`osclient`。添加配置租户前,先移除传入的 `osclient` / `OsClient` 重复键。
|
|
142
|
-
- `formData` 中发送 `OsClient`;开启 `appendOsClientQuery` 时保留接口查询参数 `?OsClient=tenant`。
|
|
143
|
-
-
|
|
142
|
+
- `formData` 中发送 `OsClient`;开启 `appendOsClientQuery` 时保留接口查询参数 `?OsClient=tenant`。
|
|
143
|
+
- 标准表单的 `ImgUpload`、`FileUpload`、`RichText` 必须通过 `formFieldContext`(或等价 `formData`)发送 `FormEngineKey + FieldId + SysMenuId`;编辑已有记录再传 `FormDataId`,TableChild 再传 JSON 化的 `_TableChildAuth`。服务端以当前租户字段配置和 FormEngine 动作授权决定实际 `Limit/Path`,SDK 不能按帐号等级强制私有,也不能信任页面传入的 `Limit=false`。
|
|
144
|
+
- 上传 `Path` 统一从 `options.path`、`formData.Path` 或 `formData.path` 归一化。
|
|
144
145
|
- 移动端上传路径必须是安全相对路径,例如 `mall/pay-proof` 或 `mall/member/avatar`。不要使用 `/mall/pay-proof`、完整 URL、磁盘路径、`..`、`:`、`//` 或 `~`。
|
|
145
146
|
- 项目薄封装要通过 `{ ...options, path: options.path || defaultPath }` 透传全部选项,避免丢失页面级 `headers`、`action`、`anonymous`、`file`、`formData` 和 `silentError`。
|
|
146
147
|
- H5 页面要保留 `uni.chooseImage` 返回的真实 `File` 对象(可用时为 `tempFiles[0].file`)。如果 H5 只返回 `tempFiles[0]` 或 `blob:` / `data:` 临时路径,也要继续传入,不要丢弃。调用 `V8.uploadFile(..., { file, preferFetch:true })`。SDK 必须识别 `File` / `Blob`、`file` / `raw` / `blob` / `originFileObj` 等常见嵌套字段,以及 `blob:` / `data:` 路径,然后优先使用 `fetch + FormData`,必要时在 `uni.uploadFile` 与 fetch 之间回退。
|
|
@@ -13,6 +13,8 @@ description: Microi 吾码 UniApp/H5 前端通用规范。用于构建或修复
|
|
|
13
13
|
|
|
14
14
|
UniApp/H5/小程序必须通过项目统一 `microi.v8.js` 的 `V8.uploadFile` / `V8.uploadFiles` 上传图片。调用方省略 `options.preview` 时,SDK 必须发送 `Preview=true` 并默认使用 `img` 路径;只有显式 `preview:false` 才允许关闭。服务端仍是最终事实源:即使旧客户端完全不传该字段,也要默认压缩到约 `500 KB`、最长边约 `1920 px`,并在 HDFS 私有桶保留一份 `_origin` 原图。页面不得为了“上传成功”自行改成 `preview:false`,也不得把私有原图或临时签名地址保存进业务字段。
|
|
15
15
|
|
|
16
|
+
动态表单上传还必须把控件已有的文件访问上下文透传为 `formFieldContext`:至少包含 `FormEngineKey + FieldId + SysMenuId`,编辑时再带 `FormDataId`,TableChild 再带 `_TableChildAuth`。后端回读 `diy_field.Config` 后决定“禁止匿名访问”与真实桶;普通用户不能被一刀切为私有桶,客户端自己传 `Limit=false` 也不能放宽私有字段。没有字段上下文的聊天、打卡、临时业务上传仍按普通私有上传处理。
|
|
17
|
+
|
|
16
18
|
## 平台方通用 App 登录连接器(明确例外)
|
|
17
19
|
|
|
18
20
|
登录页默认不得展示 ApiBase、OsClient 或调试配置;但用户明确要求、且安装包由 Microi 平台方作为多租户聚合客户端发布时,允许仅在 `APP-PLUS` 登录页提供通用连接器。协议必须使用固定 `https://` / `http://` 下拉框,地址框禁止重复输入协议,HTTPS 默认,HTTP 显示明文风险并二次确认;H5、小程序和客户专属包不得因此自动开放。候选地址先匿名读取 `GetSysConfig`,验证成功后再持久化。真正切换时必须清除旧 DiyToken、用户、SignalR、菜单/元数据/页面缓存,按 `ApiBase + OsClient` 隔离记住的账号及 RSA 密文,并用端点代次拒绝切换前的迟到响应。支持任意 HTTP 时,最终 IPA 必须显式包含 ATS 例外,Android 安装包必须显式包含 cleartext 例外;不需要任意 HTTP 的正式包应保持平台默认安全策略,iOS ATS 例外还必须在 App Store 审核中说明。
|
|
@@ -55,7 +57,7 @@ Microi 标准小程序必须采用“平台内核 + 版本化元数据 + Profile
|
|
|
55
57
|
- 自动化验收必须同时使用桌面视口与移动视口截图:桌面端断言手机壳存在,移动端断言 `.phone-status` 隐藏且 `.phone` 无圆角、无边框、宽高铺满;每个底部菜单的图标元素和文字都必须可见、可点击。
|
|
56
58
|
|
|
57
59
|
<!-- /microi-progressive:chunk -->
|
|
58
|
-
<!-- microi-progressive:chunk id=microi-uniapp-frontend-003 sha256=
|
|
60
|
+
<!-- microi-progressive:chunk id=microi-uniapp-frontend-003 sha256=5cedd05cb29746019a92db56a21123718fde23d5d9df4b8064dfc260bc89ee05 -->
|
|
59
61
|
## 登录页与手机号快捷登录
|
|
60
62
|
|
|
61
63
|
- 登录页必须是直接登录面,不要默认做“员工登录 / 客户登录”身份 Tab 切换,除非用户明确要求。默认展示系统账号密码登录,同时提供客户手机号快捷登录入口。
|
package/skills/microi.v8.js
CHANGED
|
@@ -188,14 +188,14 @@ function normalizeUploadValue(value) {
|
|
|
188
188
|
return [text];
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
function normalizeUploadData(body) {
|
|
192
|
-
const raw = Array.isArray(body && body.Data) ? (body.Data[0] || {}) : ((body && body.Data) || {});
|
|
193
|
-
const path = raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || raw.Url || raw.FileUrl || '';
|
|
194
|
-
const url = raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL || '';
|
|
195
|
-
return { ...raw, Path: path, Url: url };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function normalizeClientUploadPath(value) {
|
|
191
|
+
function normalizeUploadData(body) {
|
|
192
|
+
const raw = Array.isArray(body && body.Data) ? (body.Data[0] || {}) : ((body && body.Data) || {});
|
|
193
|
+
const path = raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || raw.Url || raw.FileUrl || '';
|
|
194
|
+
const url = raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL || '';
|
|
195
|
+
return { ...raw, Path: path, Url: url };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeClientUploadPath(value) {
|
|
199
199
|
let path = String(value || 'upload').trim().replace(/\\/g, '/');
|
|
200
200
|
if (/^(https?:|data:|blob:|file:)/i.test(path)) throw new Error('上传路径不合法。');
|
|
201
201
|
path = path.replace(/^\/+/, '').replace(/\/+$/, '').replace(/\/{2,}/g, '/');
|
|
@@ -209,30 +209,30 @@ function normalizeClientUploadPath(value) {
|
|
|
209
209
|
return parts.join('/');
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
function normalizeFileUrlData(data, assetUrl, fallback = '') {
|
|
212
|
+
function normalizeFileUrlData(data, assetUrl, fallback = '') {
|
|
213
213
|
const raw = Array.isArray(data) ? (data[0] || '') : (data || '');
|
|
214
214
|
if (typeof raw === 'string') return assetUrl(raw || fallback);
|
|
215
215
|
const url = raw.Url || raw.FileUrl || raw.FileURL || raw.PreviewUrl || raw.PreviewURL || raw.FullUrl || '';
|
|
216
216
|
const path = raw.Path || raw.FilePathName || raw.FilePath || raw.FullPath || '';
|
|
217
217
|
return assetUrl(url || path || fallback);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function isLocalPackagedAsset(path) {
|
|
221
|
-
return /^(?:\.\/)?\/?static\//i.test(String(path || ''));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function hasPublicUploadFlag(value) {
|
|
225
|
-
if (!value || typeof value !== 'object') return false;
|
|
226
|
-
const raw = Array.isArray(value) ? (value[0] || {}) : value;
|
|
227
|
-
return raw.Limit === false || raw.Limit === 0 || String(raw.Limit).toLowerCase() === 'false' ||
|
|
228
|
-
raw.IsPrivate === false || raw.Private === false || raw.Public === true;
|
|
229
|
-
}
|
|
230
|
-
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function isLocalPackagedAsset(path) {
|
|
221
|
+
return /^(?:\.\/)?\/?static\//i.test(String(path || ''));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function hasPublicUploadFlag(value) {
|
|
225
|
+
if (!value || typeof value !== 'object') return false;
|
|
226
|
+
const raw = Array.isArray(value) ? (value[0] || {}) : value;
|
|
227
|
+
return raw.Limit === false || raw.Limit === 0 || String(raw.Limit).toLowerCase() === 'false' ||
|
|
228
|
+
raw.IsPrivate === false || raw.Private === false || raw.Public === true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
231
|
function isKnownPublicUploadPath(path) {
|
|
232
232
|
// Ordinary form uploads such as /xjy/img and /xjy/file are private by default.
|
|
233
233
|
// They must use the Managed signer instead of being mistaken for CDN assets.
|
|
234
|
-
return /^\/?(?:public|mci-public|xjy\/xjy\/miniapp-assets|xjy\/miniapp\/share)\//i.test(String(path || ''));
|
|
235
|
-
}
|
|
234
|
+
return /^\/?(?:public|mci-public|xjy\/xjy\/miniapp-assets|xjy\/miniapp\/share)\//i.test(String(path || ''));
|
|
235
|
+
}
|
|
236
236
|
|
|
237
237
|
function getHeaderValue(headers, key) {
|
|
238
238
|
if (!headers) return '';
|
|
@@ -935,12 +935,12 @@ export function createMicroiV8(options = {}) {
|
|
|
935
935
|
}
|
|
936
936
|
|
|
937
937
|
// 资源地址统一过滤占位图,并兼容 HDFS 私有文件、FileServer 和绝对地址。
|
|
938
|
-
function assetUrl(value) {
|
|
938
|
+
function assetUrl(value) {
|
|
939
939
|
const picked = extractUploadPath(value);
|
|
940
940
|
if (!picked || isBlockedAsset(picked)) return '';
|
|
941
941
|
if (/^(https?:|data:|blob:|file:)/i.test(picked)) return picked;
|
|
942
|
-
if (isLocalPackagedAsset(picked)) return picked;
|
|
943
|
-
if (/^\/?file\//i.test(picked)) return joinUrl(config.apiBase, picked);
|
|
942
|
+
if (isLocalPackagedAsset(picked)) return picked;
|
|
943
|
+
if (/^\/?file\//i.test(picked)) return joinUrl(config.apiBase, picked);
|
|
944
944
|
if (/^\//.test(picked) || /^[a-z0-9_-]+\//i.test(picked)) return joinUrl(config.fileServer || config.apiBase, picked);
|
|
945
945
|
return picked;
|
|
946
946
|
}
|
|
@@ -949,59 +949,59 @@ export function createMicroiV8(options = {}) {
|
|
|
949
949
|
return config.blockedAssetPattern ? config.blockedAssetPattern.test(String(value || '')) : false;
|
|
950
950
|
}
|
|
951
951
|
|
|
952
|
-
function hasPrivateFileAccessContext(options = {}) {
|
|
953
|
-
const resourceKind = String(options.resourceKind || options.ResourceKind || 'FormField').trim();
|
|
954
|
-
const resourceId = String(options.resourceId || options.ResourceId || '').trim();
|
|
955
|
-
const formEngineKey = String(options.formEngineKey || options.FormEngineKey || '').trim();
|
|
956
|
-
const formDataId = String(options.formDataId || options.FormDataId || '').trim();
|
|
957
|
-
const fieldId = String(options.fieldId || options.FieldId || '').trim();
|
|
958
|
-
const sysMenuId = String(options.sysMenuId || options.SysMenuId || options.menuId || options.MenuId || '').trim();
|
|
959
|
-
const formFieldReady = Boolean(formEngineKey && formDataId && fieldId && sysMenuId);
|
|
960
|
-
|
|
961
|
-
if (resourceKind === 'UserAvatar' || resourceKind === 'MenuImportTemplate' || resourceKind === 'DeptImportTemplate') {
|
|
962
|
-
return Boolean(resourceId);
|
|
963
|
-
}
|
|
964
|
-
if (resourceKind === 'FileManagerObject') return Boolean(resourceId && sysMenuId);
|
|
965
|
-
if (resourceKind === 'FormFieldDerivedPreview') {
|
|
966
|
-
return formFieldReady && Boolean(options.originalFilePathName || options.OriginalFilePathName);
|
|
967
|
-
}
|
|
968
|
-
return resourceKind === 'FormField' && formFieldReady;
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
async function resolveFileUrl(filePathName, options = {}) {
|
|
972
|
-
const path = extractUploadPath(filePathName);
|
|
973
|
-
if (!path || isBlockedAsset(path)) return '';
|
|
974
|
-
if (/^(https?:|blob:|data:|file:)/i.test(path)) return assetUrl(path);
|
|
975
|
-
if (isLocalPackagedAsset(path) || options.private === false || hasPublicUploadFlag(filePathName) || isKnownPublicUploadPath(path)) {
|
|
976
|
-
return assetUrl(path);
|
|
977
|
-
}
|
|
978
|
-
// 私有对象没有权威资源上下文时直接失败关闭,禁止以裸路径换取签名。
|
|
979
|
-
if (!hasPrivateFileAccessContext(options)) return '';
|
|
980
|
-
|
|
981
|
-
async function requestPrivateFileUrl() {
|
|
982
|
-
try {
|
|
983
|
-
const body = await apiEngineRun('platform-private-file-url', {
|
|
984
|
-
OsClient: config.osClient,
|
|
985
|
-
FilePathName: path,
|
|
986
|
-
FormEngineKey: options.formEngineKey || options.FormEngineKey,
|
|
987
|
-
FormDataId: options.formDataId || options.FormDataId,
|
|
988
|
-
FieldId: options.fieldId || options.FieldId,
|
|
989
|
-
SysMenuId: options.sysMenuId || options.SysMenuId || options.menuId || options.MenuId,
|
|
990
|
-
ResourceKind: options.resourceKind || options.ResourceKind,
|
|
991
|
-
ResourceId: options.resourceId || options.ResourceId,
|
|
992
|
-
OriginalFilePathName: options.originalFilePathName || options.OriginalFilePathName,
|
|
993
|
-
_TableChildAuth: options.tableChildAuth || options._TableChildAuth,
|
|
994
|
-
HDFS: options.hdfs || options.HDFS
|
|
995
|
-
}, {
|
|
996
|
-
checkCode: false,
|
|
997
|
-
silentError: true
|
|
998
|
-
});
|
|
999
|
-
if (body && body.Code === 1 && body.Data) return normalizeFileUrlData(body.Data, assetUrl, path);
|
|
952
|
+
function hasPrivateFileAccessContext(options = {}) {
|
|
953
|
+
const resourceKind = String(options.resourceKind || options.ResourceKind || 'FormField').trim();
|
|
954
|
+
const resourceId = String(options.resourceId || options.ResourceId || '').trim();
|
|
955
|
+
const formEngineKey = String(options.formEngineKey || options.FormEngineKey || '').trim();
|
|
956
|
+
const formDataId = String(options.formDataId || options.FormDataId || '').trim();
|
|
957
|
+
const fieldId = String(options.fieldId || options.FieldId || '').trim();
|
|
958
|
+
const sysMenuId = String(options.sysMenuId || options.SysMenuId || options.menuId || options.MenuId || '').trim();
|
|
959
|
+
const formFieldReady = Boolean(formEngineKey && formDataId && fieldId && sysMenuId);
|
|
960
|
+
|
|
961
|
+
if (resourceKind === 'UserAvatar' || resourceKind === 'MenuImportTemplate' || resourceKind === 'DeptImportTemplate') {
|
|
962
|
+
return Boolean(resourceId);
|
|
963
|
+
}
|
|
964
|
+
if (resourceKind === 'FileManagerObject') return Boolean(resourceId && sysMenuId);
|
|
965
|
+
if (resourceKind === 'FormFieldDerivedPreview') {
|
|
966
|
+
return formFieldReady && Boolean(options.originalFilePathName || options.OriginalFilePathName);
|
|
967
|
+
}
|
|
968
|
+
return resourceKind === 'FormField' && formFieldReady;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
async function resolveFileUrl(filePathName, options = {}) {
|
|
972
|
+
const path = extractUploadPath(filePathName);
|
|
973
|
+
if (!path || isBlockedAsset(path)) return '';
|
|
974
|
+
if (/^(https?:|blob:|data:|file:)/i.test(path)) return assetUrl(path);
|
|
975
|
+
if (isLocalPackagedAsset(path) || options.private === false || hasPublicUploadFlag(filePathName) || isKnownPublicUploadPath(path)) {
|
|
976
|
+
return assetUrl(path);
|
|
977
|
+
}
|
|
978
|
+
// 私有对象没有权威资源上下文时直接失败关闭,禁止以裸路径换取签名。
|
|
979
|
+
if (!hasPrivateFileAccessContext(options)) return '';
|
|
980
|
+
|
|
981
|
+
async function requestPrivateFileUrl() {
|
|
982
|
+
try {
|
|
983
|
+
const body = await apiEngineRun('platform-private-file-url', {
|
|
984
|
+
OsClient: config.osClient,
|
|
985
|
+
FilePathName: path,
|
|
986
|
+
FormEngineKey: options.formEngineKey || options.FormEngineKey,
|
|
987
|
+
FormDataId: options.formDataId || options.FormDataId,
|
|
988
|
+
FieldId: options.fieldId || options.FieldId,
|
|
989
|
+
SysMenuId: options.sysMenuId || options.SysMenuId || options.menuId || options.MenuId,
|
|
990
|
+
ResourceKind: options.resourceKind || options.ResourceKind,
|
|
991
|
+
ResourceId: options.resourceId || options.ResourceId,
|
|
992
|
+
OriginalFilePathName: options.originalFilePathName || options.OriginalFilePathName,
|
|
993
|
+
_TableChildAuth: options.tableChildAuth || options._TableChildAuth,
|
|
994
|
+
HDFS: options.hdfs || options.HDFS
|
|
995
|
+
}, {
|
|
996
|
+
checkCode: false,
|
|
997
|
+
silentError: true
|
|
998
|
+
});
|
|
999
|
+
if (body && body.Code === 1 && body.Data) return normalizeFileUrlData(body.Data, assetUrl, path);
|
|
1000
1000
|
} catch (e) {}
|
|
1001
1001
|
return '';
|
|
1002
1002
|
}
|
|
1003
1003
|
|
|
1004
|
-
return (await requestPrivateFileUrl()) || '';
|
|
1004
|
+
return (await requestPrivateFileUrl()) || '';
|
|
1005
1005
|
}
|
|
1006
1006
|
|
|
1007
1007
|
// 文件上传同时支持 uni.uploadFile 与浏览器 fetch/FormData。
|
|
@@ -1009,9 +1009,9 @@ export function createMicroiV8(options = {}) {
|
|
|
1009
1009
|
const runtimeUni = getUni();
|
|
1010
1010
|
const action = options.action || (options.anonymous ? 'UniappUploadAnonymous' : 'UniappUpload');
|
|
1011
1011
|
const rawFormData = options.formData || {};
|
|
1012
|
-
const uploadData = {
|
|
1013
|
-
...rawFormData,
|
|
1014
|
-
OsClient: config.osClient,
|
|
1012
|
+
const uploadData = {
|
|
1013
|
+
...rawFormData,
|
|
1014
|
+
OsClient: config.osClient,
|
|
1015
1015
|
Limit: options.limit === false ? 'false' : 'true',
|
|
1016
1016
|
Preview: options.preview === false ? 'false' : 'true',
|
|
1017
1017
|
Multiple: options.multiple ? 'true' : 'false'
|
|
@@ -1291,7 +1291,7 @@ export function createMicroiV8(options = {}) {
|
|
|
1291
1291
|
|
|
1292
1292
|
function legacyGetCurrentUser(refresh, callback) {
|
|
1293
1293
|
if (refresh) {
|
|
1294
|
-
legacyPost('/apiengine/platform-current-user', {}, (result) => {
|
|
1294
|
+
legacyPost('/apiengine/platform-current-user', {}, (result) => {
|
|
1295
1295
|
if (result && result.Code) legacySetCurrentUser(result.Data || {});
|
|
1296
1296
|
if (typeof callback === 'function') callback(result);
|
|
1297
1297
|
});
|
|
@@ -1538,7 +1538,7 @@ export function createMicroiV8(options = {}) {
|
|
|
1538
1538
|
// 旧版前端 V8 依赖的后端接口路径,保留原名称以减少迁移成本。
|
|
1539
1539
|
const legacyApi = {
|
|
1540
1540
|
MicroiInit: '/apiengine/microi-init',
|
|
1541
|
-
GetSysConfig: '/apiengine/platform-sys-config',
|
|
1541
|
+
GetSysConfig: '/apiengine/platform-sys-config',
|
|
1542
1542
|
Login: '/api/SysUser/login',
|
|
1543
1543
|
AddFormData: '/api/FormEngine/addFormData',
|
|
1544
1544
|
AddFormDataBatch: '/api/FormEngine/addFormDataBatch',
|
|
@@ -1555,17 +1555,17 @@ export function createMicroiV8(options = {}) {
|
|
|
1555
1555
|
GetTableDataTree: '/api/FormEngine/getTableDataTree',
|
|
1556
1556
|
GetTableDataTreeAnonymous: '/api/FormEngine/getTableDataTreeAnonymous',
|
|
1557
1557
|
ApiEngineRun: '/api/ApiEngine/run',
|
|
1558
|
-
ModuleEngineRun: '/apiengine/platform-module-data',
|
|
1558
|
+
ModuleEngineRun: '/apiengine/platform-module-data',
|
|
1559
1559
|
RefreshToken: '/api/SysUser/refreshToken',
|
|
1560
1560
|
RefreshLoginUser: '/api/SysUser/refreshLoginUser',
|
|
1561
1561
|
Upload: '/api/HDFS/Upload',
|
|
1562
1562
|
UploadAnonymous: '/api/HDFS/uploadAnonymous',
|
|
1563
1563
|
UniappUpload: '/api/HDFS/UniappUpload',
|
|
1564
1564
|
UniappUploadAnonymous: '/api/HDFS/uniappUploadAnonymous',
|
|
1565
|
-
GetCurrentUser: '/apiengine/platform-current-user',
|
|
1565
|
+
GetCurrentUser: '/apiengine/platform-current-user',
|
|
1566
1566
|
GetDateTimeNow: '/api/os/getDateTimeNow',
|
|
1567
|
-
AddSysLog: '/apiengine/platform-client-log',
|
|
1568
|
-
GetOsClientByDomain: '/apiengine/platform-os-client-by-domain',
|
|
1567
|
+
AddSysLog: '/apiengine/platform-client-log',
|
|
1568
|
+
GetOsClientByDomain: '/apiengine/platform-os-client-by-domain',
|
|
1569
1569
|
ApiEngine: {}
|
|
1570
1570
|
};
|
|
1571
1571
|
|
|
@@ -1816,7 +1816,7 @@ export function createMicroiV8(options = {}) {
|
|
|
1816
1816
|
? { ModuleEngineKey: moduleKeyOrParam, ...(dataOrCallback || {}) }
|
|
1817
1817
|
: (moduleKeyOrParam || {});
|
|
1818
1818
|
const cb = typeof dataOrCallback === 'function' ? dataOrCallback : callback;
|
|
1819
|
-
return withCallback(legacyPost(legacyApi.ModuleEngineRun, { Action: 'GetTableData', ...data }), cb);
|
|
1819
|
+
return withCallback(legacyPost(legacyApi.ModuleEngineRun, { Action: 'GetTableData', ...data }), cb);
|
|
1820
1820
|
}
|
|
1821
1821
|
};
|
|
1822
1822
|
|