@microi.net/cli 4.6.2
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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/mcp-codex-stdio-adapter.js +189 -0
- package/dist/mcp-server.js +972 -0
- package/dist/mcp-trae-windows-launcher.cmd +21 -0
- package/dist/microi-cli-mcp.js +7 -0
- package/dist/microi-cli.js +1645 -0
- package/dist/microi-skills.meta.json +335 -0
- package/dist/microi.skills/.microi-skills-version.json +6 -0
- package/dist/microi.skills/README.md +276 -0
- package/dist/microi.skills/ai-engine/SKILL.md +140 -0
- package/dist/microi.skills/ai-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/app-store/SKILL.md +105 -0
- package/dist/microi.skills/app-store/agents/openai.yaml +4 -0
- package/dist/microi.skills/business-blueprint/SKILL.md +184 -0
- package/dist/microi.skills/datasource-engine/SKILL.md +89 -0
- package/dist/microi.skills/datasource-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/dos-orm/SKILL.md +76 -0
- package/dist/microi.skills/dos-orm/references/api-reference.md +229 -0
- package/dist/microi.skills/job-engine/SKILL.md +141 -0
- package/dist/microi.skills/job-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/message-notification/SKILL.md +113 -0
- package/dist/microi.skills/message-notification/agents/openai.yaml +6 -0
- package/dist/microi.skills/message-notification/references/contracts.md +99 -0
- package/dist/microi.skills/microi-ai-app-auth.js +651 -0
- package/dist/microi.skills/microi-ai-application/SKILL.md +80 -0
- package/dist/microi.skills/microi-ai-application/agents/openai.yaml +4 -0
- package/dist/microi.skills/microi-ai-application/references/frontend-baseline.md +164 -0
- package/dist/microi.skills/microi-client-frontend/SKILL.md +562 -0
- package/dist/microi.skills/microi-datasource-mapping/SKILL.md +108 -0
- package/dist/microi.skills/microi-db-schema/SKILL.md +170 -0
- package/dist/microi.skills/microi-db-schema/agents/openai.yaml +4 -0
- package/dist/microi.skills/microi-db-schema/references/core-tables.md +695 -0
- package/dist/microi.skills/microi-db-schema/references/form-component-options.md +256 -0
- package/dist/microi.skills/microi-db-schema/references/schema-overview.md +203 -0
- package/dist/microi.skills/microi-db-schema/references/schema.md +647 -0
- package/dist/microi.skills/microi-db-schema/references/table-catalog.md +1607 -0
- package/dist/microi.skills/microi-deployment/SKILL.md +117 -0
- package/dist/microi.skills/microi-deployment/references/deployment-matrix.md +94 -0
- package/dist/microi.skills/microi-docs-coverage/SKILL.md +91 -0
- package/dist/microi.skills/microi-docs-coverage/references/capability-map.md +65 -0
- package/dist/microi.skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +887 -0
- package/dist/microi.skills/microi-form-engine/SKILL.md +159 -0
- package/dist/microi.skills/microi-form-engine/references/component-catalog.md +116 -0
- package/dist/microi.skills/microi-form-engine/references/data-source-events.md +117 -0
- package/dist/microi.skills/microi-form-layout/SKILL.md +373 -0
- package/dist/microi.skills/microi-frontend-sdk/SKILL.md +304 -0
- package/dist/microi.skills/microi-left-right-layout/SKILL.md +132 -0
- package/dist/microi.skills/microi-microservice/SKILL.md +115 -0
- package/dist/microi.skills/microi-microservice/references/runtime-delivery.md +145 -0
- package/dist/microi.skills/microi-mobile-app-quality/SKILL.md +436 -0
- package/dist/microi.skills/microi-solution-quotation/SKILL.md +76 -0
- package/dist/microi.skills/microi-solution-quotation/agents/openai.yaml +4 -0
- package/dist/microi.skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -0
- package/dist/microi.skills/microi-system-delivery/SKILL.md +446 -0
- package/dist/microi.skills/microi-ui/SKILL.md +321 -0
- package/dist/microi.skills/microi-uniapp-frontend/SKILL.md +483 -0
- package/dist/microi.skills/microi.v8.js +1758 -0
- package/dist/microi.skills/module-engine/SKILL.md +131 -0
- package/dist/microi.skills/module-engine/references/module-config.md +174 -0
- package/dist/microi.skills/page-engine/SKILL.md +397 -0
- package/dist/microi.skills/performance-testing/SKILL.md +207 -0
- package/dist/microi.skills/playwright-e2e/SKILL.md +769 -0
- package/dist/microi.skills/print-engine/SKILL.md +237 -0
- package/dist/microi.skills/production-readonly-audit/SKILL.md +39 -0
- package/dist/microi.skills/report-engine/SKILL.md +69 -0
- package/dist/microi.skills/report-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/search-engine/SKILL.md +73 -0
- package/dist/microi.skills/search-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/spider-engine/SKILL.md +188 -0
- package/dist/microi.skills/translate-engine/SKILL.md +91 -0
- package/dist/microi.skills/translate-engine/agents/openai.yaml +4 -0
- package/dist/microi.skills/ui-design/SKILL.md +1575 -0
- package/dist/microi.skills/ui-design/assets/pattern-showcase/app.js +54 -0
- package/dist/microi.skills/ui-design/assets/pattern-showcase/index.html +163 -0
- package/dist/microi.skills/ui-design/assets/pattern-showcase/styles.css +311 -0
- package/dist/microi.skills/ui-design/assets/templates/MCI-DESIGN.md +98 -0
- package/dist/microi.skills/ui-design/references/design-pattern-library.md +171 -0
- package/dist/microi.skills/ui-design/references/mci-design-contract.md +84 -0
- package/dist/microi.skills/ui-design/references/motion-and-media.md +71 -0
- package/dist/microi.skills/ui-design/references/product-flow-recipes.md +94 -0
- package/dist/microi.skills/uniapp-mall-assets/SKILL.md +105 -0
- package/dist/microi.skills/v8-api-config/SKILL.md +272 -0
- package/dist/microi.skills/v8-cache-pattern/SKILL.md +286 -0
- package/dist/microi.skills/v8-crud-api/SKILL.md +398 -0
- package/dist/microi.skills/v8-debugging/SKILL.md +279 -0
- package/dist/microi.skills/v8-explorer-tree/SKILL.md +224 -0
- package/dist/microi.skills/v8-export-import/SKILL.md +590 -0
- package/dist/microi.skills/v8-file-upload/SKILL.md +497 -0
- package/dist/microi.skills/v8-formengine-http/SKILL.md +218 -0
- package/dist/microi.skills/v8-frontend-events/SKILL.md +349 -0
- package/dist/microi.skills/v8-frontend-events/references/bluetooth-print-api.md +107 -0
- package/dist/microi.skills/v8-frontend-events/references/bluetooth-print.md +185 -0
- package/dist/microi.skills/v8-http-integration/SKILL.md +379 -0
- package/dist/microi.skills/v8-image-processing/SKILL.md +187 -0
- package/dist/microi.skills/v8-image-processing/agents/openai.yaml +4 -0
- package/dist/microi.skills/v8-image-processing/references/api-reference.md +620 -0
- package/dist/microi.skills/v8-menu-buttons/SKILL.md +661 -0
- package/dist/microi.skills/v8-mongodb/SKILL.md +149 -0
- package/dist/microi.skills/v8-mq-mqtt/SKILL.md +227 -0
- package/dist/microi.skills/v8-saas-multi-tenant/SKILL.md +193 -0
- package/dist/microi.skills/v8-security/SKILL.md +417 -0
- package/dist/microi.skills/v8-sql-query/SKILL.md +290 -0
- package/dist/microi.skills/v8-table-event/SKILL.md +385 -0
- package/dist/microi.skills/v8-template-engine/SKILL.md +165 -0
- package/dist/microi.skills/v8-utilities/SKILL.md +79 -0
- package/dist/microi.skills/v8-utilities/references/client-api-index.md +136 -0
- package/dist/microi.skills/v8-utilities/references/platform-http-routes.md +80 -0
- package/dist/microi.skills/v8-utilities/references/server-api-index.md +129 -0
- package/dist/microi.skills/v8-workflow/SKILL.md +322 -0
- package/dist/microi.skills/workspace-conventions/SKILL.md +479 -0
- package/package.json +40 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# V8.Print 蓝牙打印运行指南
|
|
2
|
+
|
|
3
|
+
本参考用于 Microi 前端 V8 的 BLE 标签和小票打印。运行时事实源为
|
|
4
|
+
`Microi.Client/src/utils/v8-print.js`,指令方法事实源见
|
|
5
|
+
[`bluetooth-print-api.md`](bluetooth-print-api.md)。官网旧业务示例不能作为当前连接语义。
|
|
6
|
+
|
|
7
|
+
## 目录
|
|
8
|
+
|
|
9
|
+
- [前端挂载范围](#前端挂载范围)
|
|
10
|
+
- [运行环境与能力判断](#运行环境与能力判断)
|
|
11
|
+
- [连接与发送语义](#连接与发送语义)
|
|
12
|
+
- [最小安全流程](#最小安全流程)
|
|
13
|
+
- [批量打印与恢复](#批量打印与恢复)
|
|
14
|
+
- [兼容性与当前限制](#兼容性与当前限制)
|
|
15
|
+
- [安全边界](#安全边界)
|
|
16
|
+
- [实机验收](#实机验收)
|
|
17
|
+
|
|
18
|
+
## 前端挂载范围
|
|
19
|
+
|
|
20
|
+
当前主前端有三层真实挂载,均调用幂等的 `initV8Print(V8)`:
|
|
21
|
+
|
|
22
|
+
| 源码位置 | 覆盖的 V8 场景 |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `src/utils/diy.common.js` | 通用前端 V8 基础对象和常规按钮流程 |
|
|
25
|
+
| `src/views/form-engine/diy-form.vue` | 表单、字段及表单按钮 V8 |
|
|
26
|
+
| `src/views/form-engine/diy-table.vue` | 列表、菜单按钮、行按钮等表格 V8 |
|
|
27
|
+
|
|
28
|
+
因此不要再从租户脚本导入 `tsc.js`、`esc.js` 或自行挂载 `V8.Print`。这些能力只在
|
|
29
|
+
Microi 浏览器/5+App 前端 V8 中可用,不属于后端接口引擎、后端表单事件或微信小程序
|
|
30
|
+
原生 BLE API。
|
|
31
|
+
|
|
32
|
+
基础 V8 对象会把同一个 `Print` 状态复制给多个前端 V8 上下文。分包游标、打印份数和
|
|
33
|
+
连接引用均为可变状态,所以同一页面的所有打印任务必须共用一条串行队列,不能认为
|
|
34
|
+
不同按钮或不同 V8 对象彼此隔离。
|
|
35
|
+
|
|
36
|
+
## 运行环境与能力判断
|
|
37
|
+
|
|
38
|
+
| 运行环境 | 当前引擎 | 结论 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| 5+App 打包的 APK/IPA | `plus.bluetooth` | 支持 BLE 扫描、连接和写特征 |
|
|
41
|
+
| 存在 `navigator.bluetooth.requestDevice` 的浏览器 | Web Bluetooth | 支持;通常要求安全上下文和用户手势 |
|
|
42
|
+
| 其它普通 H5/浏览器 | 无 | `V8.Print` 仍可能存在,但连接页会提示能力不可用 |
|
|
43
|
+
| 微信小程序原生 BLE | 不属于此模块 | 需要小程序/UniApp 侧专用实现 |
|
|
44
|
+
|
|
45
|
+
不要用 `V8.ClientType === 'PC'` 判断蓝牙能力,也不要只检查
|
|
46
|
+
`BLEInformation.deviceId`。正确顺序是检查 `V8.Print`、调用 `isConnected()`,再在
|
|
47
|
+
用户点击事件中 `await OpenBluetoothPage()`。
|
|
48
|
+
|
|
49
|
+
浏览器模板、PDF、A4 单据和 Print Engine JSON 属于 `print-engine`;TSC/TSPL 或
|
|
50
|
+
ESC/POS 原生字节通过 BLE 写入才属于 `V8.Print`。
|
|
51
|
+
|
|
52
|
+
## 连接与发送语义
|
|
53
|
+
|
|
54
|
+
| API | 当前真实语义 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `createNew()` | 新建 TSC/TSPL 标签指令构建器 |
|
|
57
|
+
| `createNewESC()` | 新建 ESC/POS 小票指令构建器 |
|
|
58
|
+
| `OpenBluetoothPage()` | 返回 `Promise<boolean>`;在连接弹窗关闭时解析,值表示关闭时是否有连接信息 |
|
|
59
|
+
| `isConnected()` | Web 端检查实时 GATT 与写特征;5+App 端只检查已保存的设备/写特征 ID |
|
|
60
|
+
| `prepareSend(bytes)` | 未连接时先打开连接页,然后按包串行写入;必须 `await` 并捕获失败 |
|
|
61
|
+
| `Send(bytes)` | 依赖 `prepareSend` 已设置的内部游标,属于内部状态机入口,业务代码不要直接调用 |
|
|
62
|
+
| `setOneTimeData(bytes)` | 设置 BLE 包长;源码不校验,内置候选为 20–190、步长 10,默认 20 |
|
|
63
|
+
| `setPrinterNum(num)` | 重复发送同一缓冲区;源码不校验,内置候选为整数 1–9 |
|
|
64
|
+
| `disconnect()` | 主动断开并清理当前设备、写特征和会话元数据 |
|
|
65
|
+
| `BLEInformation` | 最近设备/服务/特征元数据,只用于诊断,不代表实时连接或打印回执 |
|
|
66
|
+
|
|
67
|
+
`OpenBluetoothPage()` 已打开时再次调用会返回 `false`。它不是“连接成功事件”;用户连上
|
|
68
|
+
设备后仍要关闭弹窗,调用方才能继续。`prepareSend` 虽会自动打开连接页,但业务按钮主动
|
|
69
|
+
建立连接更容易给出清晰提示。
|
|
70
|
+
|
|
71
|
+
连接元数据会写入 `sessionStorage`,但当前 `restoreBLEInfo()` 没有进入初始化调用链,页面
|
|
72
|
+
刷新后不会自动恢复可发送的 GATT/特征引用。刷新、跨页面重建或断线后应重新连接。
|
|
73
|
+
5+App 的 `isConnected()` 只验证 ID 是否存在,因此发送仍可能因物理断线失败。
|
|
74
|
+
|
|
75
|
+
## 最小安全流程
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
function cleanCommandText(value, maxLength) {
|
|
79
|
+
return String(value == null ? '' : value)
|
|
80
|
+
.replace(/[\r\n"\x00-\x1f]/g, ' ')
|
|
81
|
+
.slice(0, maxLength || 120);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function ensurePrinterConnected() {
|
|
85
|
+
if (!V8.Print) throw new Error('当前前端未加载蓝牙打印能力');
|
|
86
|
+
if (V8.Print.isConnected()) return;
|
|
87
|
+
|
|
88
|
+
var connected = await V8.Print.OpenBluetoothPage();
|
|
89
|
+
if (!connected || !V8.Print.isConnected()) {
|
|
90
|
+
throw new Error('未连接蓝牙打印机');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function printLabel(order) {
|
|
95
|
+
await ensurePrinterConnected();
|
|
96
|
+
|
|
97
|
+
var cmd = V8.Print.createNew();
|
|
98
|
+
cmd.setSize(60, 40);
|
|
99
|
+
cmd.setGap(2);
|
|
100
|
+
cmd.setSpeed(4);
|
|
101
|
+
cmd.setDensity(8);
|
|
102
|
+
cmd.setDirection(1);
|
|
103
|
+
cmd.setCls();
|
|
104
|
+
cmd.setText(20, 20, 'TSS24.BF2', 1, 1, cleanCommandText(order.Name, 40));
|
|
105
|
+
cmd.setBarCode(20, 80, '128', 60, 1, 2, 2, cleanCommandText(order.Code, 40));
|
|
106
|
+
cmd.setQR(340, 30, 'L', 5, 'A', cleanCommandText(order.Id, 120));
|
|
107
|
+
cmd.setPagePrint();
|
|
108
|
+
|
|
109
|
+
await V8.Print.prepareSend(cmd.getData());
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
ESC/POS 小票使用 `createNewESC()`,完整顺序和 25 个真实方法见
|
|
114
|
+
[`bluetooth-print-api.md`](bluetooth-print-api.md)。发送成功只表示 BLE 写调用完成,不能
|
|
115
|
+
写成“打印机已走纸”或“物理打印成功”。当前源码虽发现 read/notify 特征,但没有订阅状态
|
|
116
|
+
通知,也没有消费 ACK、缺纸或故障回执。
|
|
117
|
+
|
|
118
|
+
## 批量打印与恢复
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
async function printBatch(rows, startIndex) {
|
|
122
|
+
var list = Array.isArray(rows) ? rows : [];
|
|
123
|
+
var begin = Math.max(0, Number(startIndex || 0));
|
|
124
|
+
var limit = Math.min(list.length, begin + 100);
|
|
125
|
+
|
|
126
|
+
for (var i = begin; i < limit; i++) {
|
|
127
|
+
try {
|
|
128
|
+
await printLabel(list[i]);
|
|
129
|
+
V8.Tips('已发送 ' + (i + 1) + '/' + list.length, true);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
Code: 0,
|
|
133
|
+
Msg: '第 ' + (i + 1) + ' 条发送失败:' + (error.message || error),
|
|
134
|
+
NextIndex: i
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { Code: 1, Data: { NextIndex: limit, HasMore: limit < list.length } };
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- 不用固定 `setTimeout(3000)` 猜测上一张是否完成。
|
|
144
|
+
- 不用 `Promise.all`,也不要让两个按钮同时调用 `prepareSend`。
|
|
145
|
+
- 大批次分段并持久化 `NextIndex`;页面关闭、断连或写失败后从失败位置人工确认再恢复。
|
|
146
|
+
- `setPrinterNum(n)` 只适合同一缓冲区重复发送,不适合每张内容不同的批次。
|
|
147
|
+
- 业务落库与蓝牙打印不是原子事务。用稳定业务单号支持受控重打,不重复执行业务写入。
|
|
148
|
+
|
|
149
|
+
## 兼容性与当前限制
|
|
150
|
+
|
|
151
|
+
- Web Bluetooth 仅把四个常见服务 UUID 传入 `optionalServices`:`18f0`、`ff00`、
|
|
152
|
+
`49535343-fe7d-4ae5-8fa9-9fafd205e455`、`e7810a71-73ae-499d-8c15-faa9aef0c3f2`。
|
|
153
|
+
当前没有公开的自定义服务配置,并选择枚举到的第一个可写特征;其它型号可能需要扩展源码。
|
|
154
|
+
- `prepareSend` 默认每包 20 字节、包间约 20ms;同一缓冲区多份打印间约 100ms。这只是
|
|
155
|
+
BLE 写节奏,不是打印完成等待时间。包长必须是已实测的正整数,空缓冲区不得发送。
|
|
156
|
+
- 当前分包公式是 `floor(length / packetSize) + 1`。长度恰好整除包长时会尝试额外写一个
|
|
157
|
+
0 字节末包;某些 BLE 栈会拒绝。实机出现此问题时应修复适配器并回归,不要靠并发或吞错绕过。
|
|
158
|
+
- TSC 与 ESC 文本使用仓库内置 `encoding.js` + `encoding-indexes.js` 转为 GB18030,运行时
|
|
159
|
+
不请求网络。编码成功不等于打印机字体、代码页和固件支持全部字符;Emoji 等仍需实机验证。
|
|
160
|
+
- `setBitmap` 接受 ImageData 风格 `{ width, height, data }` RGBA 数据。当前黑白转换较简单,
|
|
161
|
+
大图可能产生大缓冲区;先缩放、二值化并用小图测试。
|
|
162
|
+
- `V8.Print` 没有任务锁和队列。跨 V8 上下文并发会互相覆盖 `currentTime`、`looptime`、
|
|
163
|
+
`lastData` 等共享状态,必须由业务侧全局串行化。
|
|
164
|
+
|
|
165
|
+
## 安全边界
|
|
166
|
+
|
|
167
|
+
- TSC 的 `setText`、`setQR`、`setBarCode` 和 `addCommand` 会拼协议文本。移除引号、换行、
|
|
168
|
+
NUL/控制字符并限制长度;`addCommand` 只接受固定、受审查的命令。
|
|
169
|
+
- 蓝牙设备名称、ID 和服务特征均是外部输入。不要拼入 `innerHTML`,展示时做文本转义;不要
|
|
170
|
+
记录或上传完整 `BLEInformation`,以免泄露终端指纹。
|
|
171
|
+
- 金额、数量、坐标、纸张尺寸、包长和份数先做类型/范围校验,避免无限循环或超大缓冲区。
|
|
172
|
+
- 打印内容含个人信息、票据或密钥时,不写控制台、系统日志或异常上报正文。
|
|
173
|
+
- 浏览器权限拒绝、用户取消、GATT 断开、找不到服务/特征和写包失败都必须可理解地提示。
|
|
174
|
+
|
|
175
|
+
## 实机验收
|
|
176
|
+
|
|
177
|
+
至少记录:
|
|
178
|
+
|
|
179
|
+
1. 打印机品牌、型号、固件、纸张规格、服务/写特征 UUID 和指令集。
|
|
180
|
+
2. 5+App 或浏览器版本;首次授权、再次连接、主动断开、页面刷新和断线重连。
|
|
181
|
+
3. 中文、数字、特殊字符、二维码、条码、长文本、图片和边界金额。
|
|
182
|
+
4. 默认 20 字节与目标包长;同时覆盖“长度恰好整除包长”。
|
|
183
|
+
5. 连续 20 张严格串行发送,无乱序、丢包、重复或任务状态互相污染。
|
|
184
|
+
6. 中途关机、缺纸、离开范围、权限撤销后的失败位置与恢复行为。
|
|
185
|
+
7. 页面只确认“数据已发送”;若业务要求确认物理结果,另接状态回读或人工确认。
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: v8-http-integration
|
|
3
|
+
description: Microi V8 HTTP 集成指南。用于通过 V8.Http.Get/Post/Patch、对应 Response 方法及后端 Async 方法调用接口,处理请求头、JSON/form/XML 载荷、超时、文件和响应解析,并兼容前后端 V8。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Microi V8 HTTP 外部接口集成
|
|
7
|
+
|
|
8
|
+
你正在开发 Microi 吾码平台的 V8 引擎代码,需要调用外部 HTTP API(微信、支付宝、短信、ERP 等第三方系统)。
|
|
9
|
+
|
|
10
|
+
文档维护时,前端用法更新现有 `microi.doc/docs/doc/v8-engine/v8-client.md`,后端用法更新现有 `microi.doc/docs/doc/v8-engine/v8-server.md`;不要新建重复的 V8.Http 文档页面或路由。只维护中文 `docs/doc/`,英文 `docs/en/` 由官网统一翻译生成。
|
|
11
|
+
|
|
12
|
+
## V8.Http API
|
|
13
|
+
|
|
14
|
+
| 方法 | 说明 | 返回值 |
|
|
15
|
+
|------|------|--------|
|
|
16
|
+
| `V8.Http.Get({...})` | GET 请求 | 字符串(响应体) |
|
|
17
|
+
| `V8.Http.Post({...})` | POST 请求 | 字符串(响应体) |
|
|
18
|
+
| `V8.Http.Patch({...})` | PATCH 请求 | 字符串(响应体) |
|
|
19
|
+
| `V8.Http.GetResponse({...})` | GET(完整响应) | `{ Content, Headers, StatusCode }` |
|
|
20
|
+
| `V8.Http.PostResponse({...})` | POST(完整响应) | `{ Content, Headers, StatusCode }` |
|
|
21
|
+
| `V8.Http.PatchResponse({...})` | PATCH(完整响应) | `{ Content, Headers, StatusCode }` |
|
|
22
|
+
|
|
23
|
+
后端接口引擎还提供真实异步方法:
|
|
24
|
+
|
|
25
|
+
| 方法 | 说明 |
|
|
26
|
+
|------|------|
|
|
27
|
+
| `await V8.Http.GetAsync({...})` | 异步 GET,返回响应字符串 |
|
|
28
|
+
| `await V8.Http.PostAsync({...})` | 异步 POST,返回响应字符串 |
|
|
29
|
+
| `await V8.Http.PatchAsync({...})` | 异步 PATCH,返回响应字符串 |
|
|
30
|
+
| `await V8.Http.GetResponseAsync({...})` | 异步 GET,返回完整响应 |
|
|
31
|
+
| `await V8.Http.PostResponseAsync({...})` | 异步 POST,返回完整响应 |
|
|
32
|
+
| `await V8.Http.PatchResponseAsync({...})` | 异步 PATCH,返回完整响应 |
|
|
33
|
+
| `await V8.Http.GetStreamAsync({...})` | 异步获取响应流,供当前请求内继续处理 |
|
|
34
|
+
|
|
35
|
+
前端与后端统一使用 PascalCase 对象参数格式。执行模型不同:后端既可调用同步方法,也可在本次请求内调用显式 `*Async` 方法并 `await`;前端浏览器调用无 `Async` 后缀的方法,但必须 `await` 其 `Promise`。旧版前端 `V8.Post/Get` 继续兼容,不得删除。
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// 后端接口引擎:请求内异步 I/O
|
|
39
|
+
var resp = await V8.Http.PostResponseAsync({
|
|
40
|
+
Url: 'https://api.example.com/orders',
|
|
41
|
+
PostParam: { OrderNo: V8.Param.orderNo },
|
|
42
|
+
ParamType: 'json',
|
|
43
|
+
Timeout: 10
|
|
44
|
+
});
|
|
45
|
+
if (resp.StatusCode < 200 || resp.StatusCode >= 300) {
|
|
46
|
+
return { Code: 0, Msg: '上游接口调用失败' };
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
异步方法只保证当前请求内等待完成,不能替代 Job、MQ、outbox 或平台后台任务。不得用未等待的 Promise、`setTimeout` 或 `Task.Run` 实现“响应后继续处理”。
|
|
51
|
+
|
|
52
|
+
通用参数:
|
|
53
|
+
|
|
54
|
+
| 参数 | 说明 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `Url` | 必传。后端通常使用绝对地址;前端支持相对当前 `ApiBase` 的地址和绝对地址。 |
|
|
57
|
+
| `GetParam` | URL 查询参数;GET、POST、PATCH 均可使用。 |
|
|
58
|
+
| `PostParam` / `PatchParam` | POST / PATCH 对象请求体。 |
|
|
59
|
+
| `PostParamString` / `PatchParamString` | 已序列化的 JSON 或 XML 请求体,嵌套 JSON 优先使用。 |
|
|
60
|
+
| `ParamType` | `form`(默认)、`json`、`xml`、`binary`。 |
|
|
61
|
+
| `Timeout` / `TimeOut` | 超时秒数;默认 `600` 秒(10 分钟)。 |
|
|
62
|
+
| `Headers` / `Header` | 请求头对象,两种参数名兼容。 |
|
|
63
|
+
| `FilesByteBase64` / `FilesByteString` | 文件字段对象,键同时作为字段名和文件名。 |
|
|
64
|
+
|
|
65
|
+
`GetResponse/PostResponse/PatchResponse` 返回 `Content`、`Headers`、`RawBytes`、`StatusCode`、`ErrorMessage`。后端 `RawBytes` 是 `.NET byte[]`,前端是 `Uint8Array`。
|
|
66
|
+
|
|
67
|
+
## POST 请求(对象参数格式)
|
|
68
|
+
|
|
69
|
+
> V8 接口引擎中必须使用对象参数格式。尤其禁止 `V8.Http.Get(url)`:当前 .NET 同名重载包含 `Task<string> Get(string)`,Jint 可能把字符串调用解析为异步重载,脚本最终拿到 `[object Promise]`。GET 必须写成 `V8.Http.Get({ Url: url })`;第三方登录、微信 `jscode2session`、AccessToken 等链路保存后必须用无效 code 烟测,确认返回的是第三方明确错误而不是 Promise。
|
|
70
|
+
|
|
71
|
+
第三方授权链路还必须把身份交换、AccessToken、用户资料/手机号交换拆成独立阶段。每阶段分别捕获 HTTP 异常和第三方业务 `errcode/errmsg`,失败时写带追踪号的脱敏系统日志并把明确原因返回前端;禁止只在最外层返回固定“登录失败”。
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// POST JSON(推荐使用对象参数格式)
|
|
75
|
+
var result = V8.Http.Post({
|
|
76
|
+
Url: 'https://api.example.com/users', // 必传
|
|
77
|
+
PostParam: { name: '张三', phone: '13800001234' }, // form 参数(不支持多级嵌套)
|
|
78
|
+
ParamType: 'json', // 请求类型:默认 form,可选 json / xml
|
|
79
|
+
Timeout: 600, // 超时秒数,默认 600 秒(10 分钟)
|
|
80
|
+
Headers: { Authorization: 'Bearer ' + token } // 请求头
|
|
81
|
+
});
|
|
82
|
+
var data = JSON.parse(result);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## PATCH 请求
|
|
86
|
+
|
|
87
|
+
PATCH 与 POST 的参数完全对称,只需把请求体参数改为 `PatchParam` / `PatchParamString`:
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
// 后端接口引擎:同步返回字符串
|
|
91
|
+
var result = V8.Http.Patch({
|
|
92
|
+
Url: 'https://api.example.com/users/123',
|
|
93
|
+
PatchParamString: JSON.stringify({ profile: { name: '新名字' } }),
|
|
94
|
+
ParamType: 'json',
|
|
95
|
+
Timeout: 10,
|
|
96
|
+
Headers: { Authorization: 'Bearer ' + token }
|
|
97
|
+
});
|
|
98
|
+
var data = JSON.parse(result);
|
|
99
|
+
|
|
100
|
+
// 前端 V8:参数相同,但浏览器请求必须 await
|
|
101
|
+
var result = await V8.Http.Patch({
|
|
102
|
+
Url: '/api/users/123',
|
|
103
|
+
PatchParam: { Status: 1 },
|
|
104
|
+
ParamType: 'json'
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### POST 嵌套 JSON 对象
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// 多级嵌套对象需使用 PostParamString
|
|
112
|
+
var result = V8.Http.Post({
|
|
113
|
+
Url: 'https://api.example.com/complex',
|
|
114
|
+
PostParamString: JSON.stringify({
|
|
115
|
+
user: { name: '张三', address: { city: '北京' } }
|
|
116
|
+
}),
|
|
117
|
+
ParamType: 'json'
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### POST XML
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
var result = V8.Http.Post({
|
|
125
|
+
Url: 'https://api.example.com/xml',
|
|
126
|
+
ParamType: 'xml',
|
|
127
|
+
PostParamString: '<xml><text>内容</text></xml>'
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### POST 上传文件
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
var result = V8.Http.Post({
|
|
135
|
+
Url: 'https://api.example.com/upload',
|
|
136
|
+
PostParam: { name: '附件' },
|
|
137
|
+
FilesByteBase64: { file: 'Base64编码的文件内容' }
|
|
138
|
+
// 或 FilesByteString: { file: '文件字节字符串' }
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## GET 请求
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
// 对象参数格式
|
|
146
|
+
var result = V8.Http.Get({
|
|
147
|
+
Url: 'https://api.example.com/users',
|
|
148
|
+
GetParam: { page: 1, size: 20 }, // URL 查询参数
|
|
149
|
+
Timeout: 10,
|
|
150
|
+
Headers: { Authorization: 'Bearer ' + token }
|
|
151
|
+
});
|
|
152
|
+
var data = JSON.parse(result);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 获取完整响应(含状态码和响应头)
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
var resp = V8.Http.PostResponse({
|
|
159
|
+
Url: 'https://api.example.com/submit',
|
|
160
|
+
PostParamString: JSON.stringify({ orderId: V8.Param.orderId }),
|
|
161
|
+
ParamType: 'json'
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (resp.StatusCode !== 200) {
|
|
165
|
+
return { Code: 0, Msg: '第三方接口返回 ' + resp.StatusCode };
|
|
166
|
+
}
|
|
167
|
+
// resp.Content — 响应内容(字符串)
|
|
168
|
+
// resp.Headers — 响应头数组 [{ Name: '', Value: '' }]
|
|
169
|
+
// resp.StatusCode — HTTP 状态码
|
|
170
|
+
|
|
171
|
+
var data = JSON.parse(resp.Content);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
PATCH 完整响应写法相同:
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
var resp = V8.Http.PatchResponse({
|
|
178
|
+
Url: 'https://api.example.com/users/123',
|
|
179
|
+
PatchParam: { status: 'enabled' },
|
|
180
|
+
ParamType: 'json'
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
前端调用时写为 `await V8.Http.PostResponse(...)` 或 `await V8.Http.PatchResponse(...)`。
|
|
185
|
+
|
|
186
|
+
### 后端 SSRF 与重定向边界
|
|
187
|
+
|
|
188
|
+
- 严格 SSRF 防护默认关闭;未配置时完全保留历史行为,不限制协议、URL 内嵌凭据、回环、私网、链路本地或云元数据地址,并继续自动处理重定向。
|
|
189
|
+
- 只有在 SaaS 引擎主租户启用 `SsrfProtectionEnabled` 后,后端才只允许 HTTP(S),拒绝 URL 内嵌凭据、回环、私网、链路本地、云元数据和其它特殊地址,并禁止自动跟随 3xx。
|
|
190
|
+
- 严格模式需要跳转时读取完整响应的 `StatusCode` / `Headers`,经业务判断后显式发起下一次调用,使每一跳重新校验。
|
|
191
|
+
- 严格模式的 SaaS 字段 `SsrfAllowedHosts` 只能加入受控且固定的精确主机,不接受用户输入,不要配置通配。
|
|
192
|
+
- DNS 校验不能代替网络层出站 ACL。生产环境还应在容器、主机或网关阻断云元数据和非必要私网段。
|
|
193
|
+
|
|
194
|
+
## 前端 V8 行为与兼容性
|
|
195
|
+
|
|
196
|
+
- 前端新代码应优先使用 `await V8.Http.Get/Post/Patch`,参数与后端一致;不要再把 `V8.Post/Get` 作为新功能首选。旧 `V8.Post/Get` 仅作为兼容 API 保留,其回调和 Promise 写法保持不变。
|
|
197
|
+
- 相对地址或当前 `ApiBase` 地址会沿用吾码登录头,并接收响应中的新 `authorization`;第三方绝对地址不会自动携带吾码 Token,避免凭据泄漏。
|
|
198
|
+
- 浏览器请求第三方地址受 CORS 限制;这是浏览器安全策略,后端 `V8.Http` 不受浏览器 CORS 限制。
|
|
199
|
+
- 前端字符串方法同后端一样返回原始响应文本,不会自动 `JSON.parse`;需要对象时显式解析。
|
|
200
|
+
- 浏览器端不支持后端的 `FilesStream`,可使用 `FilesByteBase64`、`FilesByteString` 或 `FilesByte`。
|
|
201
|
+
|
|
202
|
+
## 下载远程文件(图片、PDF 等二进制)
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
var resp = V8.Http.GetResponse({
|
|
206
|
+
Url: 'https://example.com/file.png',
|
|
207
|
+
Timeout: 30
|
|
208
|
+
});
|
|
209
|
+
if (resp.StatusCode !== 200) return { Code: 0, Msg: '下载失败' };
|
|
210
|
+
|
|
211
|
+
var bytes = resp.RawBytes; // .NET byte[]
|
|
212
|
+
var base64 = System.Convert.ToBase64String(bytes);
|
|
213
|
+
|
|
214
|
+
// 转存到 HDFS
|
|
215
|
+
var up = V8.Method.Upload({
|
|
216
|
+
FilesByteBase64: { 'remote.png': base64 },
|
|
217
|
+
Limit: false, Path: '/imported', OsClient: V8.OsClient
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
> 文件上传/下载完整模式见 `v8-file-upload/SKILL.md`
|
|
222
|
+
|
|
223
|
+
## 第三方密钥不要硬编码
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
// ❌ 危险:密钥写死在代码
|
|
227
|
+
var apiKey = 'sk-xxxxxxxx';
|
|
228
|
+
|
|
229
|
+
// ✅ 正确:放在 SaaS 引擎的 OsClientModel
|
|
230
|
+
var apiKey = V8.OsClientModel.OpenAIKey;
|
|
231
|
+
var secret = V8.OsClientModel.WxPaySecret;
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
详见 `v8-saas-multi-tenant/SKILL.md`
|
|
235
|
+
```javascript
|
|
236
|
+
// GET 完整响应
|
|
237
|
+
var resp = V8.Http.GetResponse({
|
|
238
|
+
Url: 'https://api.example.com/data',
|
|
239
|
+
GetParam: { id: '123' }
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## 实战模式
|
|
244
|
+
|
|
245
|
+
### 微信小程序 access_token
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
var cacheKey = 'Microi:' + V8.OsClient + ':wx_access_token';
|
|
249
|
+
var token = V8.Cache.Get(cacheKey);
|
|
250
|
+
|
|
251
|
+
if (!token) {
|
|
252
|
+
var appId = V8.OsClientModel.WxAppId; // 敏感配置存在 SaaS 引擎中
|
|
253
|
+
var secret = V8.OsClientModel.WxAppSecret;
|
|
254
|
+
var result = V8.Http.Get({
|
|
255
|
+
Url: 'https://api.weixin.qq.com/cgi-bin/token',
|
|
256
|
+
GetParam: { grant_type: 'client_credential', appid: appId, secret: secret }
|
|
257
|
+
});
|
|
258
|
+
var data = JSON.parse(result);
|
|
259
|
+
|
|
260
|
+
if (data.access_token) {
|
|
261
|
+
token = data.access_token;
|
|
262
|
+
V8.Cache.Set(cacheKey, token, '0.01:56:00'); // 缓存 1 小时 56 分钟
|
|
263
|
+
} else {
|
|
264
|
+
return { Code: 0, Msg: '获取 access_token 失败: ' + (data.errmsg || '') };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return { Code: 1, Data: { access_token: token } };
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 签名验证(HmacSHA256)
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
var timestamp = V8.Action.GetTimestamp().toString();
|
|
275
|
+
var nonce = System.Guid.NewGuid().ToString().replace(/-/g, '').substring(0, 16);
|
|
276
|
+
var body = JSON.stringify({ orderId: V8.Param.orderId });
|
|
277
|
+
var signStr = timestamp + '\n' + nonce + '\n' + body + '\n';
|
|
278
|
+
var signature = V8.EncryptHelper.HmacSha256(apiSecret, signStr);
|
|
279
|
+
|
|
280
|
+
var result = V8.Http.Post({
|
|
281
|
+
Url: 'https://api.example.com/pay',
|
|
282
|
+
PostParamString: body,
|
|
283
|
+
ParamType: 'json',
|
|
284
|
+
Headers: {
|
|
285
|
+
'X-Timestamp': timestamp,
|
|
286
|
+
'X-Nonce': nonce,
|
|
287
|
+
'X-Signature': signature
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 调用其他 Microi 接口引擎
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
// 不需要 HTTP,直接内部调用(可共享事务)
|
|
296
|
+
var result = V8.ApiEngine.Run('calculate-price', {
|
|
297
|
+
productId: V8.Param.productId,
|
|
298
|
+
quantity: V8.Param.quantity
|
|
299
|
+
}, V8.DbTrans);
|
|
300
|
+
|
|
301
|
+
if (result.Code !== 1) {
|
|
302
|
+
return { Code: 0, Msg: '价格计算失败: ' + result.Msg };
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Webhook 回调处理
|
|
307
|
+
|
|
308
|
+
```javascript
|
|
309
|
+
// 接收外部 Webhook(将此引擎作为 Webhook URL)
|
|
310
|
+
var payload = V8.Param;
|
|
311
|
+
|
|
312
|
+
V8.Method.AddSysLog({
|
|
313
|
+
Title: 'Webhook',
|
|
314
|
+
Content: JSON.stringify(payload),
|
|
315
|
+
Type: 'third-party'
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
if (payload.event === 'payment.success') {
|
|
319
|
+
V8.FormEngine.UptFormDataByWhere('OrderHeader', {
|
|
320
|
+
_Where: [['OrderNo', '=', payload.order_no]],
|
|
321
|
+
PayStatus: 'paid',
|
|
322
|
+
PayTime: DateNow('yyyy-MM-dd HH:mm:ss')
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return { Code: 1, Msg: 'ok' };
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## V8.Office.SendEmail — 发送邮件
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
V8.Office.SendEmail({
|
|
333
|
+
SmtpServer: 'smtp.qq.com',
|
|
334
|
+
SmtpPort: 587,
|
|
335
|
+
EnableSSL: true,
|
|
336
|
+
SystemEmail: 'admin@itdos.com',
|
|
337
|
+
SystemEmailPwd: 'password',
|
|
338
|
+
EmailSubject: '邮件标题',
|
|
339
|
+
EmailBody: '<b>HTML内容</b>',
|
|
340
|
+
Receivers: ['123@qq.com', '456@qq.com']
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## 错误处理模式
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
try {
|
|
348
|
+
var response = V8.Http.Post({
|
|
349
|
+
Url: url,
|
|
350
|
+
PostParamString: body,
|
|
351
|
+
ParamType: 'json',
|
|
352
|
+
Timeout: 10
|
|
353
|
+
});
|
|
354
|
+
var result = JSON.parse(response);
|
|
355
|
+
|
|
356
|
+
if (result.code !== 0) {
|
|
357
|
+
console.error('Third-party API error: ' + response);
|
|
358
|
+
return { Code: 0, Msg: '第三方接口错误: ' + (result.message || result.msg || '') };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return { Code: 1, Data: result.data };
|
|
362
|
+
} catch (ex) {
|
|
363
|
+
console.error('HTTP request failed: ' + ex.message);
|
|
364
|
+
return { Code: 0, Msg: '请求第三方接口失败,请稍后重试' };
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## 注意事项
|
|
369
|
+
|
|
370
|
+
- `V8.Http.Post` 的 `PostParam` 不支持多级嵌套对象,嵌套需用 `PostParamString`
|
|
371
|
+
- `V8.Http.Patch` 的 `PatchParam` 不支持多级嵌套对象,嵌套需用 `PatchParamString`
|
|
372
|
+
- `Headers` 参数也可以写成 `Header`(两者等效)
|
|
373
|
+
- 前端 `V8.Http` 必须使用 `await`;后端接口引擎无需 `await`
|
|
374
|
+
- 旧版前端 `V8.Post/Get` 是兼容 API,不能删除;但新代码必须优先使用 `V8.Http`
|
|
375
|
+
- 第三方 API 密钥建议存在 `V8.OsClientModel`(SaaS 引擎)中,不要硬编码
|
|
376
|
+
- 调用外部接口应加 try-catch,第三方服务不可控
|
|
377
|
+
- 对于需要缓存的 token(如微信 access_token),使用 `V8.Cache` 避免频繁请求
|
|
378
|
+
- 缓存过期时间格式为 `d.HH:mm:ss`,如 `0.01:00:00` 表示 1 小时
|
|
379
|
+
- 内部接口引擎之间的调用用 `V8.ApiEngine.Run()`,不需要 HTTP
|