@microi.net/cli 6.1.0 → 6.1.1

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.
Files changed (39) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/assets/build-meta.json +4 -4
  7. package/cordis.patch.yml +1 -1
  8. package/package.json +1 -1
  9. package/scripts/mcp-server.js +78 -78
  10. package/scripts/microi-skills.meta.json +230 -230
  11. package/skills/.microi-skills-version.json +2 -2
  12. package/skills/.progressive-disclosure-manifest.json +39 -39
  13. package/skills/README.md +287 -287
  14. package/skills/ai-engine/SKILL.md +273 -273
  15. package/skills/app-store/SKILL.md +531 -531
  16. package/skills/message-notification/SKILL.md +1 -1
  17. package/skills/microi-client-frontend/SKILL.md +19 -19
  18. package/skills/microi-codex-installer/SKILL.md +233 -233
  19. package/skills/microi-datasource-mapping/SKILL.md +9 -0
  20. package/skills/microi-deployment/SKILL.md +1 -1
  21. package/skills/microi-microservice/SKILL.md +353 -353
  22. package/skills/microi-sso/references/acceptance.md +51 -51
  23. package/skills/microi-system-delivery/SKILL.md +115 -115
  24. package/skills/microi-system-delivery/references/context-quality.md +54 -54
  25. package/skills/microi-system-delivery/references/progressive-03-/345/256/211/345/205/250/344/270/216/350/267/250/347/253/257/351/252/214/346/224/266.md +65 -65
  26. package/skills/page-engine/SKILL.md +24 -24
  27. package/skills/playwright-e2e/SKILL.md +2 -0
  28. package/skills/unity-integration/references/ai-app-delivery.md +119 -119
  29. package/skills/v8-api-config/SKILL.md +400 -400
  30. package/skills/v8-formengine-http/SKILL.md +238 -238
  31. package/skills/v8-saas-multi-tenant/SKILL.md +309 -305
  32. package/skills/v8-security/SKILL.md +2 -0
  33. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +9 -3
  34. package/skills/v8-tcp-integration/SKILL.md +225 -158
  35. package/skills/v8-utilities/SKILL.md +105 -104
  36. package/skills/v8-utilities/references/server-api-index.md +189 -188
  37. package/skills/workspace-conventions/SKILL.md +168 -168
  38. package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +32 -32
  39. package/skills/workspace-conventions/references/progressive-04-/345/267/245/344/275/234/345/214/272/344/270/216/345/271/263/345/217/260/344/270/223/351/241/271/344/277/235/351/232/234.md +188 -188
@@ -50,6 +50,8 @@ Preview 和确认均重验当前客户端、回调及 Scope;账号变化通过
50
50
 
51
51
  官方升级资源属于独立控制面。`get-microi-upgrade-resource` 可以匿名读取固定白名单,但 `Publish/PublishBatch` 必须调用仅绑定该 Managed ApiEngineKey 的 `V8.Method.AuthorizeOfficialResourcePublish()`:固定 `iTdos` 官方租户、拒绝访问密钥会话,并从主库复核当前用户、状态和平台管理员角色。禁止只相信 `V8.CurrentUser.Level`,也禁止把这个可信原子复用于普通接口、租户 Hook 或表单事件;资源校验、SHA 乐观锁、事务行锁、写入及回读仍由 Managed V8 编排。
52
52
 
53
+ 同一 Managed 会话接口包含本人初始化与敏感管理动作时,精确 `ApiRole` 不能代替动作级鉴权。SaaS v8.4.26 / `platform-sys-user-session` v1.0.5 对已归一化的 `GetSysUserPassword`,先拒绝匿名和访问密钥,再以可信 `V8.CurrentUser`、`V8.Db` 主库的本人活动状态/等级及有效管理员角色核验后调用原可信原子;不读取密码字段做授权,不使用 `DbRead`、客户端身份字段或角色名判断。标准角色授权可用于只读账号的本人会话初始化,不能把整引擎授权解释成敏感动作许可。负例使用预先确认不存在的固定用户 Id:普通账号必须先被管理员门拒绝,管理员仅验证合法路径返回不存在,禁止为验收读取任何真实密码。
54
+
53
55
  <!-- microi-progressive:begin -->
54
56
  <!-- microi-progressive:chunk id=v8-security-000 sha256=a6a4311b1a799ea4ed005b22c01cb8230509aaf09aeead5a17c8c46da74772c8 -->
55
57
  ## 0. 租户动态系统设置与密钥边界
@@ -2,9 +2,15 @@
2
2
 
3
3
  > 按需读取;本文件由 SKILL.md 的原章节无损拆分。
4
4
 
5
- <!-- microi-progressive:chunk id=v8-security-005 sha256=657db8189d96c594073735e9182af9d3e9a91f4f7dd111ecf2c7fe1eec6d7ede -->
5
+ <!-- microi-progressive:chunk id=v8-security-005 sha256=e0f600f6c4d6b654e358f6da229ad60d6814f198f1566c8009948072b1e6e9c2 -->
6
6
  ## 2. 权限校验
7
7
 
8
+ ### 历史管理员等级兼容
9
+
10
+ `admin` 内置账号和名称精确为“超级管理员”的有效角色必须分别为 `9999`;还要核对账号 `RoleIds` 的权威绑定与等级快照。普通角色名称包含“管理员”不构成升级依据。数据库兼容步骤 `7.6.17.0` 独立于商城恢复门槛,按共享租约、事务、回读、授权 epoch 失效和登录投影刷新执行,失败不推进该步骤版本。
11
+
12
+ 只有旧版本(早于 `7.6.15.0` 或无版本)且用户、角色均未出现 `Level>=9999` 时,才转换其它旧 `999/998` 为 `9999/9998`。新版可能把 `999/998` 用于普通业务权限,禁止无条件提升。生产专项修复先读取账号、角色、绑定用户和版本,再按明确授权精确处理;主租户可通过受控接口引擎的 `FormEngine` 显式指定子租户,必须固定目标、复核管理员身份、记录审计并回读,不能把主租户能力开放为任意跨租户参数。
13
+
8
14
  ### DiyToken 是平台会话与权限入口,不替换为 ASP.NET Identity
9
15
 
10
16
  DiyToken 与 `sys_user`、`OsClient`、终端 `did`、共享 Redis 登录态、角色/部门、菜单动作、表权限、数据范围和接口引擎共同构成吾码认证授权体系。它不是“只有一个 Token 字符串”的简化登录:
@@ -37,7 +43,7 @@ DiyToken 与 `sys_user`、`OsClient`、终端 `did`、共享 Redis 登录态、
37
43
  - 菜单 `SqlWhere`、`SqlJoin` / `JoinTables` 数据范围必须在服务端形成的**真实列表、计数和导出查询**中执行,不能只用于界面展示或查询后过滤。单行详情只校验同表菜单访问权,不应用这些模块列表过滤;它们也不是行级写权限。
38
44
  - 主表新增、修改、删除分别由当前角色的 `Add`、`Edit`、`Del` 权限控制;不得把查询 SqlWhere 追加到写入 SQL,也不得因为查询包含跨表 Join 拒绝已获授权的写入。需要“仅可修改本人数据”等业务限制时,在 `SubmitBeforeServerV8` 或专用接口引擎中以可信服务器代码校验,并统一写入 `TenantId`、负责人、创建人等归属字段。
39
45
  - 导入、导出必须携带真实菜单上下文,并分别拥有 `Import`、`Export`;不能用 Table 级直接授权绕过。
40
- - 平台表必须按后端 `PlatformResourceSecurity` 分级:角色/权限、SaaS 配置、接口引擎、任务、数据源、密钥和基础设施等管理员专用表,对 `Level < 9999` 的通用客户端 FormEngine 全操作硬拒绝;`diy_table/diy_field`、工作流、微服务/商店、蓝图和微应用运行元数据只允许显式授权后的 `Read/List`,增删改及导入导出仍硬拒绝;`mic_page/mic_print/sys_user` 按真实菜单或 Table 的 `Read/Add/Edit/Del` 权限管理,账号管理另外执行层级校验和密码脱敏。
46
+ - 平台表必须按后端 `PlatformResourceSecurity` 分级:角色/权限、SaaS 配置、任务、数据源、密钥和基础设施等管理员专用表,对 `Level < 9999` 的通用客户端 FormEngine 全操作硬拒绝;`diy_table/diy_field`、工作流、微服务/商店、蓝图和微应用运行元数据只允许显式授权后的 `Read/List`,增删改及导入导出仍硬拒绝;`sys_apiengine/mic_page/mic_print/sys_user` 按真实菜单或 Table 的各项操作权限管理,账号管理另外执行层级校验和密码脱敏。接口引擎菜单授权必须在服务端生效,不能要求给已授权开发角色提级到 9999;只读不等于代码写权限。
41
47
  - 需要角色查询表/字段目录时,在 `/#/system/role` 的【表直连权限】选择 `diy_table/diy_field` 并仅授予【查】,不必强制改用接口引擎。授权包含配置与事件源码,仅授予可信角色;选列参数不构成字段权限,需限制返回字段时仍用固定投影的接口引擎。此策略由后端发布,前端读取 `GetDirectTableGrantPolicies`,不维护第二份表名白名单;不得自动给存量角色补授权。
42
48
  - 匿名读取/新增仅适用于 `diy_table` 明确开启匿名能力的普通业务表;上述三类平台表必须先于匿名开关拒绝。
43
49
  - 角色增删改接口不能只相信 Token 缓存或前端禁用状态,必须覆盖请求中的 `_CurrentUser/OsClient`,并从租户主库复核活动用户、数据库 Level 与有效角色 Level。角色降级要先同步受影响用户 Level,再提升共享授权 `epoch`,避免旧令牌窗口;Postman 伪造 `_IsAdmin/Level/RoleIds` 必须失败。
@@ -47,7 +53,7 @@ DiyToken 与 `sys_user`、`OsClient`、终端 `did`、共享 Redis 登录态、
47
53
 
48
54
  跨租户公共发现接口可以匿名,但返回值必须采用严格字段白名单,只返回公开标识和展示信息,禁止返回 `ApiKey`、上游 Endpoint、连接串或内部配置。例如官方 AI 中转模型清单应保持 `IsEnable=1`、`StopHttp=0`、`AllowAnonymous=1`,且只返回模型 Id、展示名等公开字段;将这类被其它租户服务器调用的发现接口误改为非匿名,会让所有消费者得到空模型列表或 `NoAuth`,属于破坏性配置变更。发布和升级后必须用无 Token 的真实 HTTP 请求验证返回字段与匿名状态。
49
55
 
50
- > 后端接口引擎、后端表单 V8 和平台内部调用由服务端在构造参数时设置不可由 HTTP JSON 注入的 `_TrustedServerInvocation`,因此调用 `V8.FormEngine` 不要求 `_SysMenuId`。`_InvokeType:'Server'` 只是事件调用语义,不是外部客户端可用的授权开关;普通用户即使在请求中伪造这两个字段也不能成为可信调用。能写 V8、接口引擎、任务或数据源的普通管理入口仍限制为 `Level >= 9999`。MCP 唯一例外是上节明确授权的 Full 范围且同时具有创建/修改、删除权限的可信开发角色,并须通过 MCP 专用逐请求鉴权;不能由菜单授权或请求参数推导此例外。
56
+ > 后端接口引擎、后端表单 V8 和平台内部调用由服务端在构造参数时设置不可由 HTTP JSON 注入的 `_TrustedServerInvocation`,因此调用 `V8.FormEngine` 不要求 `_SysMenuId`。`_InvokeType:'Server'` 只是事件语义,伪造它不能获得信任。接口引擎标准 FormEngine 管理允许精确菜单/表操作权限委托;代码写权限意味着可信服务端执行能力,仅授予可信开发角色。其它控制面管理员门禁、接口运行权限不因此放宽。MCP 仍须独立验证 Full 范围及创建/修改、删除等专用开发授权,不能从菜单权限推导 MCP 授权。
51
57
 
52
58
  > 外层 `AddFormData` 的客户端菜单/`Add` 权限校验与事件内部执行权是两件事:前者防止无新增权限的用户进入事件,后者允许已经进入的 `SubmitBeforeServerV8` / `SubmitAfterServerV8` 像接口引擎一样在当前租户内执行复杂 SQL、跨表事务和其它表 CRUD。不要把客户端菜单范围再次套到服务器 V8 的嵌套调用。
53
59
 
@@ -1,158 +1,225 @@
1
- ---
2
- name: v8-tcp-integration
3
- description: Microi 后端 V8 TCP 原始字节集成规范。用于通过 V8.Tcp.Send/SendAsync/SendAndReceive/SendAndReceiveAsync 连接网络小票机、串口服务器、PLC 或其它 TCP 设备,处理 Byte/Base64/Hex/Text 载荷、GBK/GB18030、超时、响应、幂等与出站安全。
4
- ---
5
-
6
- > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
7
-
8
- # Microi V8 TCP 原始字节集成
9
-
10
- 使用后端 `V8.Tcp` 完成一次性 TCP 连接、原始字节写入和可选的有界响应读取。典型场景是 RAW/JetDirect 9100 网络小票机、串口服务器和 PLC;具体协议帧、校验码与打印业务仍由接口引擎编排。
11
-
12
- 文档维护时,只在 `microi.doc/docs/doc/v8-engine/v8-server.md` 原位更新后端 API;不要新建重复页面,也不要手工维护英文文档。改变公共契约时同步后端源码/测试、Microi.Client Monaco 定义、Microi.Agent 类型与生成知识、AI Skill 镜像。
13
-
14
- ## 先判断是否适用
15
-
16
- | 需求 | 选择 |
17
- |---|---|
18
- | 后端向设备 TCP 端口发送原始字节 | `V8.Tcp` |
19
- | HTTP/HTTPS API | `V8.Http`,不要用 TCP 重写 HTTP |
20
- | RabbitMQ/MQTT | `V8.MQ` 或 MQTT 引擎 |
21
- | 浏览器/5+App 蓝牙或 SPP 打印 | 前端 `V8.Print`,读取 `v8-frontend-events/references/bluetooth-print.md` |
22
- | 设备原始 TCP 上报并保持连接等待应答 | API 宿主可选的 `TcpIngressRulesJson` 入站监听;固定租户与接口引擎,协议帧交给接口引擎处理 |
23
- | TLS、任意协议、可由 V8 动态开端口、自定义连接池 | 当前不提供;单独评估安全边界和传输实现 |
24
-
25
- 平台已经提供可复用原子能力后,打印模板、设备选择、订单状态、幂等和审计优先写在接口引擎/表单事件;不要为单个打印业务再写 C# Controller。
26
-
27
- ## API 容器内的原始 TCP 入站
28
-
29
- `V8.Tcp` 仍然仅是出站客户端。设备只能填主机/IP 与端口、不能发送 HTTP 请求路径时,可在同一个 `Microi.net.Api` 进程启用独立原始 TCP 监听;HTTP 的 `1052` 等端口不变,TCP 必须使用另一个端口。入口由主租户 `sys_osclients.TcpIngressEnable`、`TcpIngressPort` 与 `TcpIngressRulesJson` 配置:启用状态空值/1 允许加载完整规则、0 明确关闭;端口留空时容器内默认 `1885`;规则留空不监听、坏规则只使 TCP `Faulted`。规则 JSON 不得含 `enabled` 或 `listenPort`。运行中每约 30 秒独立回查主库开关,设 `0` 后下一轮关闭 TCP 监听与会话,HTTP 继续;从 `0` 改回空值/1、或修改端口/规则/Key/CIDR,不会热重绑,必须手工重启现有 API 容器。只允许主租户管理员维护,配置不得进入 `V8.OsClientModel` 投影。
30
-
31
- 入站配置固定 `osClient`、`apiEngineKey`、来源 CIDR、帧头/帧尾、U16BE 长度字段、CRC16/XMODEM 与请求/响应关联区间;独立的 `TcpIngressPort` 固定容器内监听端口。设备报文绝不能决定租户或调用哪个接口引擎。来源网段优先使用已核实的 IPv4 窄网段;确实无法按来源 IP 限制时,只有在规则 JSON 中显式设置 `allowAnySource:true` 且 `allowedCidrs` **仅**为 `["0.0.0.0/0"]` 才允许任意 IPv4 来源。省略/设为 `false` 时,`/0` 不合法;`/0` 也不能与其它网段混写。这是高风险例外,不是默认推荐或设备鉴权。监听器以固定 `_InvokeType=Server` 内部调用已启用的接口引擎,启动时必须验证目标 `StopHttp=1`、`AllowAnonymous=0`,否则 TCP 局部失败关闭、不监听,以免匿名 HTTP 绕过 TCP 来源约束;HTTP API 保持运行。接口引擎接收 `frameHex`,把原始应答字节的十六进制放在 `Data.ResponseFrameHex`;协议明确的失败应答即使业务 `Code=0` 也需要回传。没有合法应答、校验不通过或超时则关闭连接,不制造成功回执。拆包、粘包、连续心跳均须逐帧处理并在原连接回包。
32
-
33
- 上线顺序:先部署并启动新 API,确认 `sys_config.ServerVersion >= 7.6.16.0` 且旧 `MqttEnable` 空值已迁为 `0`,再通过商城安装/更新 SaaS 包并发布带 `NullValue=1` 的前端;物理迁移不会自动更新旧租户 UI 元数据。新 TCP 包未安装时空规则不监听、HTTP 继续。然后明确设主租户 `TcpIngressEnable=0` 且暂留空规则;核实旧 HTTP 地址没有合法调用方,或先迁移它们,再**仅对目标接口**设置 `StopHttp=1`、`AllowAnonymous=0` 并验证匿名 HTTP 被拒;最后填写完整 TCP 规则、设 `TcpIngressEnable=1`、配置宿主端口及来源策略并重启同一容器。旧 HTTP 仍被使用时不得直接切换。其它厂商的接口与端口不作改动,但 API 容器重启后要逐条回归。
34
-
35
- 启用前优先限制来源网段与云安全组,设置帧长、连接数和空闲时间上限;CRC、设备号都不是身份认证。经 CLB 时核对后端实际看到的源 IP,且当前不解析 Proxy Protocol v2 报头。全网例外必须有业务接口内的设备身份/授权校验、幂等与异常连接监控;如果协议本身没有不可伪造的设备凭据,应明确记录剩余风险,并在能识别来源时收窄规则及云侧放行。Docker 在同一个 API service 显式增加如 `19011:1885/tcp`(宿主机对外端口:容器内端口)的映射;不要把 `/apiengine/...` URL、HTTP 的 `80/1052` 或 `V8.Tcp.Send` 当作设备端 TCP 监听。新执行的在线一键安装器会在 `61600+` 连续宿主端口段末尾追加 MQTT/TCP 映射;离线默认宿主 `7010/7011`,都映射至 API 容器 `1883/1885` 并尝试放行宿主机防火墙。窄来源方案仍需云安全组/CLB 人工限源;已有 Compose 不会自动补映射,SaaS 内部端口改变后须同步 Compose。TCP 配置、引擎安全位或端口失败时仅 TCP 状态为 `Faulted`,HTTP 仍可健康;验收必须确认 TCP `Listening` 状态/监听日志、端口探测和合法心跳应答,再核对设备数据。只连上端口不能证明已回 ACK;当前没有对外的 TCP 状态 HTTP 端点,不能把 HTTP 健康当成 TCP 就绪。配置、回环测试、设备联调的详细步骤见官网中文后端 V8 页面;本地回环不等于客户真机已联网。
36
-
37
- ## API 契约
38
-
39
- | 方法 | 行为 |
40
- |---|---|
41
- | `V8.Tcp.Send({...})` | 连接、发送、关闭,返回 `DosResult` |
42
- | `await V8.Tcp.SendAsync({...})` | 当前请求内异步发送 |
43
- | `V8.Tcp.SendAndReceive({...})` | 发送后读取响应,再关闭 |
44
- | `await V8.Tcp.SendAndReceiveAsync({...})` | 当前请求内异步发送并读取响应 |
45
-
46
- 必填目标参数:
47
-
48
- - `Host`:主机名或 IP;只允许主机,不传 `tcp://` URL。
49
- - `Port`:1-65535。
50
-
51
- 发送内容必须且只能选择一组:
52
-
53
- - `Bytes`(别名 `RawBytes`、`ByteArray`):每项为 0-255 的整数。
54
- - `ByteBase64`(别名 `BytesBase64`、`Base64`)。
55
- - `Hex`:允许空格、`0x`、`-`、`:`、逗号和下划线分隔。
56
- - `Text`:按 `Encoding` 编码。默认 `utf-8`,还支持 `ascii`、`gb18030`、`gbk/gb2312`、UTF-16/UTF-32 大小端。
57
-
58
- 边界参数:
59
-
60
- | 参数 | 默认 | 范围 |
61
- |---|---:|---:|
62
- | `ConnectTimeout` | 10 秒 | 1-120 秒 |
63
- | `SendTimeout` | 10 秒 | 1-120 秒 |
64
- | `ReceiveTimeout` | 3 秒 | 1-120 秒 |
65
- | `MaxReceiveBytes` | 65536 | 1-1048576 |
66
- | `NoDelay` | `true` | 布尔值 |
67
-
68
- 单次发送最多 4 MiB。`Send` 成功数据为 `{BytesSent, RemoteEndPoint}`。收发方法另有 `BytesReceived`、`RawBytes`、`ByteBase64`、`Hex`、`ReceiveEndReason`、`Truncated`;`ReceiveEndReason` 为 `RemoteClosed`、`Timeout` 或 `MaxReceiveBytes`。
69
-
70
- ## 实现流程
71
-
72
- ### 1. 固定并授权设备目标
73
-
74
- 从 SaaS 可信配置或受行权限保护的设备表读取 `Host`、`Port`,按当前用户/门店/租户决定可用设备,再与精确白名单比较。禁止这样写:
75
-
76
- ```javascript
77
- // 错误:匿名或普通调用者可扫描后端可达内网。
78
- return V8.Tcp.Send({
79
- Host: V8.Param.Host,
80
- Port: V8.Param.Port,
81
- Hex: V8.Param.Hex
82
- });
83
- ```
84
-
85
- ### 2. 形成一个完整帧
86
-
87
- ESC/POS、Modbus TCP 或设备私有协议的命令、正文和校验码应先合成一个完整字节载荷。不要为了拼帧而打开多次连接;包含中文和控制命令时,优先在可信后端/上游把完整帧编码为 `Bytes`、`ByteBase64` 或 `Hex`。
88
-
89
- ```javascript
90
- var result = V8.Tcp.Send({
91
- Host: '192.168.1.88', // 示例;生产从可信配置读取
92
- Port: 9100,
93
- Bytes: [
94
- 27, 64, // ESC @ 初始化
95
- 77, 105, 99, 114, 111, 105, 10,
96
- 10, 10,
97
- 29, 86, 0 // GS V 0 切纸
98
- ],
99
- ConnectTimeout: 5,
100
- SendTimeout: 5
101
- });
102
- if (result.Code !== 1) return result;
103
- ```
104
-
105
- 只有纯文本时可使用打印机常见编码:
106
-
107
- ```javascript
108
- var result = await V8.Tcp.SendAsync({
109
- Host: '192.168.1.88',
110
- Port: 9100,
111
- Text: '吾码小票\n合计:12.00\n\n',
112
- Encoding: 'gb18030'
113
- });
114
- if (result.Code !== 1) return result;
115
- ```
116
-
117
- ### 3. 需要协议响应时显式收取
118
-
119
- ```javascript
120
- var result = V8.Tcp.SendAndReceive({
121
- Host: '192.168.1.20',
122
- Port: 4001,
123
- Hex: '01 03 00 00 00 02 C4 0B',
124
- ReceiveTimeout: 3,
125
- MaxReceiveBytes: 4096
126
- });
127
- if (result.Code !== 1) return result;
128
-
129
- var bytes = result.Data.RawBytes;
130
- var hex = result.Data.Hex;
131
- ```
132
-
133
- 接收超时是整段接收的总时间。已收到部分数据后超时会返回成功和已有数据,并标记 `ReceiveEndReason='Timeout'`;未收到任何字节则失败。协议必须知道响应长度或结束条件,不能把超时默认当作完整帧。
134
-
135
- ### 4. 分开判断写入、设备执行和实物结果
136
-
137
- - `Code=1` 只表示字节已写入 TCP 连接。
138
- - 设备协议回执可证明设备接受/处理到哪一步,但仍不一定证明机械动作完成。
139
- - “小票已打印”需要打印机状态、设备回执或实物/硬件验收证据。
140
-
141
- 打印不是天然幂等。不确定失败时自动重试可能重复出纸;需要可靠打印时使用业务打印单号、设备回执、状态机以及 Job/MQ/outbox,且重试策略必须识别“尚未发送”和“结果未知”。
142
-
143
- ## 安全与运行约束
144
-
145
- 1. TCP 不经过 `V8.Http` 的 SSRF 防护。容器网络策略/防火墙只放行目标设备网段和端口;匿名接口不得暴露任意目标。
146
- 2. 不把票据正文、设备口令、原始帧和响应秘密写入日志;日志只记录脱敏设备标识、业务单号、字节数、耗时和错误分类。
147
- 3. `SendAsync` 只是在当前请求内等待 I/O,不能替代后台任务。不要遗留未等待 Promise、`setTimeout` 或 `Task.Run`。
148
- 4. 每次调用都会创建并关闭连接;不能假设设备会保持会话状态。
149
- 5. Docker 内的 `localhost` 指容器自身。验收前从实际后端容器验证到设备 IP/端口的路由和出站策略。
150
- 6. 载荷、超时和响应均有硬上限;不要通过拆成无限循环规避限制。
151
-
152
- ## 修改公共能力时的同步门禁
153
-
154
- - 源码:`Microi.Server/Microi.V8Engine/Extend/Tcp/V8Tcp.cs` 与 `V8Extend.cs`。
155
- - 测试:真实 Jint 注入、Byte/Base64/Hex/Text、回环收发、超时/上限/非法参数。
156
- - 编辑器:`Microi.Client/.../v8-api-server-definitions.js` 与 `Microi.Agent/src/editor/typingsManager.ts`。
157
- - 文档与知识:官网中文后端 V8 页面、`microi.skills/README.md`、`v8-utilities` 索引、`Microi.AI/Resource` 镜像和向量资源列表。
158
- - 验收:目标测试、后端构建、精确重启与健康检查、独立 TCP 回环;真实打印机出纸必须单独报告,不能用回环测试替代。
1
+ ---
2
+ name: v8-tcp-integration
3
+ description: Microi 后端 TCP 与通用 IoT 集成规范。用于 V8.Tcp 一次性客户端、V8.IoT.Send 设备原连接下行、可配置拆帧/校验、跨节点路由,以及 MQTT/HTTP 能力选择、设备权限、幂等和真实验收。
4
+ ---
5
+
6
+ > **Microi吾码基础规范(强制):** 任何 AI 模型与宿主每次新建或接续吾码任务,先完整读取 `../workspace-conventions/SKILL.md`,必须执行版本播报、`@microi.net/cli` 后台自动升级、Skills/MCP 同步和进度播报。安装与诊断读取 `../microi-codex-installer/SKILL.md`;更新失败延后重试,不阻断当前工作。
7
+
8
+ # Microi V8 TCP 原始字节集成
9
+
10
+ 使用后端 `V8.Tcp` 完成一次性 TCP 连接、原始字节写入和可选的有界响应读取。典型场景是 RAW/JetDirect 9100 网络小票机、串口服务器和 PLC;具体协议帧、校验码与打印业务仍由接口引擎编排。
11
+
12
+ 文档维护时,只在 `microi.doc/docs/doc/v8-engine/v8-server.md` 原位更新后端 API;不要新建重复页面,也不要手工维护英文文档。改变公共契约时同步后端源码/测试、Microi.Client Monaco 定义、Microi.Agent 类型与生成知识、AI Skill 镜像。
13
+
14
+ ## 先判断是否适用
15
+
16
+ | 需求 | 选择 |
17
+ |---|---|
18
+ | 后端向设备 TCP 端口发送原始字节 | `V8.Tcp` |
19
+ | HTTP/HTTPS API | `V8.Http`,不要用 TCP 重写 HTTP |
20
+ | RabbitMQ/MQTT | `V8.MQ` 或 MQTT 引擎 |
21
+ | 浏览器/5+App 蓝牙或 SPP 打印 | 前端 `V8.Print`,读取 `v8-frontend-events/references/bluetooth-print.md` |
22
+ | 设备原始 TCP 上报并保持连接等待应答 | API 宿主可选的 `TcpIngressRulesJson` 入站监听;固定租户与接口引擎,协议帧交给接口引擎处理 |
23
+ | 设备已主动连接平台,需要发送控制命令 | `V8.IoT.Send`,沿已授权原连接下行;不依赖某个厂家 |
24
+ | TLS、任意协议、可由 V8 动态开端口、自定义连接池 | 当前不提供;单独评估安全边界和传输实现 |
25
+
26
+ 平台已经提供可复用原子能力后,打印模板、设备选择、订单状态、幂等和审计优先写在接口引擎/表单事件;不要为单个打印业务再写 C# Controller。
27
+
28
+ ## API 容器内的原始 TCP 入站
29
+
30
+ `V8.Tcp` 仍然仅是出站客户端。设备只能填主机/IP 与端口、不能发送 HTTP 请求路径时,可在同一个 `Microi.net.Api` 进程启用独立原始 TCP 监听;HTTP 的 `1052` 等端口不变,TCP 必须使用另一个端口。入口由主租户 `sys_osclients.TcpIngressEnable`、`TcpIngressPort` 与 `TcpIngressRulesJson` 配置:启用状态空值/1 允许加载完整规则、0 明确关闭;端口留空时容器内默认 `1885`;规则留空不监听、坏规则只使 TCP `Faulted`。规则 JSON 不得含 `enabled` 或 `listenPort`。运行中每约 30 秒独立回查主库开关,设 `0` 后下一轮关闭 TCP 监听与会话,HTTP 继续;从 `0` 改回空值/1、或修改端口/规则/Key/CIDR,不会热重绑,必须手工重启现有 API 容器。只允许主租户管理员维护,配置不得进入 `V8.OsClientModel` 投影。
31
+
32
+ 入站配置固定 `osClient`、`apiEngineKey`、来源 CIDR、帧头/帧尾、U16BE 长度字段、CRC16/XMODEM 与请求/响应关联区间;独立的 `TcpIngressPort` 固定容器内监听端口。设备报文绝不能决定租户或调用哪个接口引擎。来源网段优先使用已核实的 IPv4 窄网段;确实无法按来源 IP 限制时,只有在规则 JSON 中显式设置 `allowAnySource:true` 且 `allowedCidrs` **仅**为 `["0.0.0.0/0"]` 才允许任意 IPv4 来源。省略/设为 `false` 时,`/0` 不合法;`/0` 也不能与其它网段混写。这是高风险例外,不是默认推荐或设备鉴权。监听器以固定 `_InvokeType=Server` 内部调用已启用的接口引擎,启动时必须验证目标 `StopHttp=1`、`AllowAnonymous=0`,否则 TCP 局部失败关闭、不监听,以免匿名 HTTP 绕过 TCP 来源约束;HTTP API 保持运行。接口引擎接收 `frameHex`,把原始应答字节的十六进制放在 `Data.ResponseFrameHex`;协议明确的失败应答即使业务 `Code=0` 也需要回传。没有合法应答、校验不通过或超时则关闭连接,不制造成功回执。拆包、粘包、连续心跳均须逐帧处理并在原连接回包。
33
+
34
+ 上线顺序:先部署并启动新 API,确认 `sys_config.ServerVersion >= 7.6.16.0` 且旧 `MqttEnable` 空值已迁为 `0`,再通过商城安装/更新 SaaS 包并发布带 `NullValue=1` 的前端;物理迁移不会自动更新旧租户 UI 元数据。新 TCP 包未安装时空规则不监听、HTTP 继续。然后明确设主租户 `TcpIngressEnable=0` 且暂留空规则;核实旧 HTTP 地址没有合法调用方,或先迁移它们,再**仅对目标接口**设置 `StopHttp=1`、`AllowAnonymous=0` 并验证匿名 HTTP 被拒;最后填写完整 TCP 规则、设 `TcpIngressEnable=1`、配置宿主端口及来源策略并重启同一容器。旧 HTTP 仍被使用时不得直接切换。其它厂商的接口与端口不作改动,但 API 容器重启后要逐条回归。
35
+
36
+ 启用前优先限制来源网段与云安全组,设置帧长、连接数和空闲时间上限;CRC、设备号都不是身份认证。经 CLB 时核对后端实际看到的源 IP,且当前不解析 Proxy Protocol v2 报头。全网例外必须有业务接口内的设备身份/授权校验、幂等与异常连接监控;如果协议本身没有不可伪造的设备凭据,应明确记录剩余风险,并在能识别来源时收窄规则及云侧放行。Docker 在同一个 API service 显式增加如 `19011:1885/tcp`(宿主机对外端口:容器内端口)的映射;不要把 `/apiengine/...` URL、HTTP 的 `80/1052` 或 `V8.Tcp.Send` 当作设备端 TCP 监听。新执行的在线一键安装器会在 `61600+` 连续宿主端口段末尾追加 MQTT/TCP 映射;离线默认宿主 `7010/7011`,都映射至 API 容器 `1883/1885` 并尝试放行宿主机防火墙。窄来源方案仍需云安全组/CLB 人工限源;已有 Compose 不会自动补映射,SaaS 内部端口改变后须同步 Compose。TCP 配置、引擎安全位或端口失败时仅 TCP 状态为 `Faulted`,HTTP 仍可健康;验收必须确认 TCP `Listening` 状态/监听日志、端口探测和合法心跳应答,再核对设备数据。只连上端口不能证明已回 ACK;当前没有对外的 TCP 状态 HTTP 端点,不能把 HTTP 健康当成 TCP 就绪。配置、回环测试、设备联调的详细步骤见官网中文后端 V8 页面;本地回环不等于客户真机已联网。
37
+
38
+ ## 已建立 IoT 连接的主动下行
39
+
40
+ `V8.IoT` 是受限的设备原连接下行扩展。
41
+ 设备主动连接平台且需要云端控制时,使用 `V8.IoT.Send({DeviceId, CommandId, FrameHex, ExpireSeconds})`,
42
+ 不能用 `V8.Tcp.Send` 新建连接代替原设备连接。须配套 Core、V8Engine 和 API 宿主更新;
43
+ 缺失 `TcpIngressRulesJson.downlink` 或 `enabled:false` 时不启用,修改配置后重启现有 API。
44
+
45
+ `V8.IoT` 负责连接路由与传输状态;操作码、流水号、厂家 HTTP/MQTT 选择、设备权限和回执
46
+ 放在接口引擎。禁止在 Core/V8Engine 中新增按厂家名、业务表名或设备编号分支。
47
+ MQTT 设备继续使用 MQTT 引擎,平台主动连接设备使用 `V8.Tcp`,厂家云接口使用 `V8.Http`。
48
+
49
+ 接入另一协议先读取厂家真实说明,再检查既有 `framing` 描述是否覆盖:
50
+
51
+ - 省略 `framing` 保持历史 U16BE/XMODEM,不改变既有配置含义。
52
+ - 显式 `framing.mode` 支持 `LengthPrefix`、`FixedLength`、`Delimiter`;帧头 0–32 字节、帧尾 0–16 字节。
53
+ - `lengthBytes` 为 1/2/4,`lengthByteOrder` 为 BigEndian/LittleEndian;长度加 `lengthAdjustment` 得总帧长。
54
+ - `fixedLength` 为固定帧总长;结束符模式必须有帧尾且载荷不含未转义结束符。
55
+ - `checksum` 为 Crc16Xmodem/Crc16Modbus/None;`checksumByteOrder` 与 `checksumStartOffset` 可配置。
56
+ - 上报地址由 `deviceOffset/deviceLength` 定义;下行偏移可用 `downlinkDeviceOffset`,地址不能与结构字段重叠。
57
+ - 普通 ACK 关联规则继续严格执行;主动下行用同一结构描述单独核对设备,不通过缩短关联区间绕过校验。
58
+ - 当前每个 API 进程只有一套监听规则;没有单端口多协议自动探测、TLS 或任意转义解码。
59
+ 超出能力时补通用适配器,不声称全部协议均已支持;完整配置示例与限制见官网后端 V8 主文档。
60
+
61
+ ```javascript
62
+ // 已完成设备行权限/操作权限校验,且业务指令已提交;由受权派发接口调用。
63
+ return V8.IoT.Send({
64
+ DeviceId: deviceAddressHex,
65
+ CommandId: committedCommandId,
66
+ FrameHex: completeProtocolFrameHex,
67
+ ExpireSeconds: 30
68
+ });
69
+ ```
70
+
71
+ - `downlink` 必须声明 `deviceOffset`、`deviceLength` 和 `sendApiEngineKeys`;其余队列、有效期、
72
+ 去重、租约和轮询限额见官网中文后端 V8 原页面。新设置继续使用既有 SaaS 规则,不加环境变量。
73
+ - 内部上报引擎确认设备身份和绑定后,每个成功响应显式返回 `Data.IoTDeviceId` 及正常
74
+ `Data.ResponseFrameHex`;地址必须与物理帧匹配。ACK 写入后才登记下行连接;明确免应答消息按下文处理。
75
+ - 控制接口必须校验当前用户的设备与操作权限;原子从可信执行上下文获取租户、接口 Key、
76
+ 登录用户或可信任务身份,并按管理员的 Key 白名单约束调用。禁止透传前端任意报文。
77
+ - `DeviceId` 为定长设备地址十六进制,由业务记录映射;`CommandId` 稳定且长期持久去重。
78
+ `FrameHex` 为无分隔符的完整协议帧。仅四个参数允许传入,租户/Host/Port 不接受。
79
+ - `Code=1` 只表示接受指令或发现同编号既有指令。`Queued`、`Sending`、`Written`、
80
+ `Unknown`、`Expired` 是传输状态;设备执行结果由协议回执确认。`Sending/Unknown` 禁止换号重发。
81
+ - 业务记录/outbox 先提交,再由派发任务调用原子;Redis 和设备动作不能随数据库回滚。
82
+ Redis 队列绑定连接代次,旧连接不能清除新连接租约,已经开始发送的指令不自动重投。
83
+ - 保持正常 ACK 的关联规则;主动下行独立校验目标、结构和 CRC,无需缩短原关联长度。
84
+ 原 ACK 与控制指令串行写入同一 Socket,重连与 Redis 故障必须覆盖。
85
+ - 单元/Socket 回环、真实 Redis 多节点、真实设备回执和物理动作是不同验收层;不得相互替代。
86
+
87
+ ### 多控制功能与免应答回执
88
+
89
+ - `V8.IoT.Send` 不筛选具体命令/操作码。设备开关、温度/阈值、定时数组、模式、报警提示等,
90
+ 共用原子;命令目录、字段宽度/顺序/单位、机型范围和权限由接口引擎编排,不再加厂家专属 C# 方法。
91
+ - 区分“报警上报/恢复”“远程报警提示开关”“消音/故障复位”。协议只有上报时,不能虚构清警指令,
92
+ 也不能以修改平台报警记录代替设备故障消除。
93
+ - 业务记录保存稳定 `CommandId` 与设备流水号映射;回执按设备、回执命令、子码、流水号及长度匹配。
94
+ 不同命令的成功码、流水号位置甚至应答命令可能不同,未知值保持未知,不统一写死。
95
+ - 温度范围、型号/固件语义或报文长度不明确的动作默认拒绝,等待厂家确认后修改可信配置。
96
+ 前端不能上传命令目录、范围、成功码、完整报文或可执行表达式覆盖后端规则。
97
+ - 控制回执无需平台再 ACK 时,受保护规则根节点显式配置 `allowNoResponse:true`,内部接口逐帧返回
98
+ `{Code:1,Data:{IoTDeviceId:authorizedAddressHex,ResponseMode:'None'}}`;不提供 `ResponseFrameHex` 属性。
99
+ 默认关闭且必须布尔值,更新后重启 API;只有数值 Code=1 和有效帧才接受,仍复核设备授权。
100
+ - `None` 表示消息已处理且零字节写回,不能代表设备已执行;缺失响应、空返回、失败或错误模式不能冒充 None。
101
+ 正常应答继续使用 `ResponseFrameHex`(可选 `ResponseMode:'Reply'`);心跳仍按厂家协议正常回 ACK。
102
+ - 回归覆盖可变载荷、不同回执格式、回执后继续心跳/控制、授权撤销、无效模式和存量失败 ACK。
103
+
104
+ ## API 契约:一次性 TCP 客户端
105
+
106
+ | 方法 | 行为 |
107
+ |---|---|
108
+ | `V8.Tcp.Send({...})` | 连接、发送、关闭,返回 `DosResult` |
109
+ | `await V8.Tcp.SendAsync({...})` | 当前请求内异步发送 |
110
+ | `V8.Tcp.SendAndReceive({...})` | 发送后读取响应,再关闭 |
111
+ | `await V8.Tcp.SendAndReceiveAsync({...})` | 当前请求内异步发送并读取响应 |
112
+
113
+ 必填目标参数:
114
+
115
+ - `Host`:主机名或 IP;只允许主机,不传 `tcp://` URL。
116
+ - `Port`:1-65535。
117
+
118
+ 发送内容必须且只能选择一组:
119
+
120
+ - `Bytes`(别名 `RawBytes`、`ByteArray`):每项为 0-255 的整数。
121
+ - `ByteBase64`(别名 `BytesBase64`、`Base64`)。
122
+ - `Hex`:允许空格、`0x`、`-`、`:`、逗号和下划线分隔。
123
+ - `Text`:按 `Encoding` 编码。默认 `utf-8`,还支持 `ascii`、`gb18030`、`gbk/gb2312`、UTF-16/UTF-32 大小端。
124
+
125
+ 边界参数:
126
+
127
+ | 参数 | 默认 | 范围 |
128
+ |---|---:|---:|
129
+ | `ConnectTimeout` | 10 秒 | 1-120 秒 |
130
+ | `SendTimeout` | 10 秒 | 1-120 秒 |
131
+ | `ReceiveTimeout` | 3 秒 | 1-120 秒 |
132
+ | `MaxReceiveBytes` | 65536 | 1-1048576 |
133
+ | `NoDelay` | `true` | 布尔值 |
134
+
135
+ 单次发送最多 4 MiB。`Send` 成功数据为 `{BytesSent, RemoteEndPoint}`。收发方法另有 `BytesReceived`、`RawBytes`、`ByteBase64`、`Hex`、`ReceiveEndReason`、`Truncated`;`ReceiveEndReason` 为 `RemoteClosed`、`Timeout` 或 `MaxReceiveBytes`。
136
+
137
+ ## 实现流程
138
+
139
+ ### 1. 固定并授权设备目标
140
+
141
+ 从 SaaS 可信配置或受行权限保护的设备表读取 `Host`、`Port`,按当前用户/门店/租户决定可用设备,再与精确白名单比较。禁止这样写:
142
+
143
+ ```javascript
144
+ // 错误:匿名或普通调用者可扫描后端可达内网。
145
+ return V8.Tcp.Send({
146
+ Host: V8.Param.Host,
147
+ Port: V8.Param.Port,
148
+ Hex: V8.Param.Hex
149
+ });
150
+ ```
151
+
152
+ ### 2. 形成一个完整帧
153
+
154
+ ESC/POS、Modbus TCP 或设备私有协议的命令、正文和校验码应先合成一个完整字节载荷。不要为了拼帧而打开多次连接;包含中文和控制命令时,优先在可信后端/上游把完整帧编码为 `Bytes`、`ByteBase64` 或 `Hex`。
155
+
156
+ ```javascript
157
+ var result = V8.Tcp.Send({
158
+ Host: '192.168.1.88', // 示例;生产从可信配置读取
159
+ Port: 9100,
160
+ Bytes: [
161
+ 27, 64, // ESC @ 初始化
162
+ 77, 105, 99, 114, 111, 105, 10,
163
+ 10, 10,
164
+ 29, 86, 0 // GS V 0 切纸
165
+ ],
166
+ ConnectTimeout: 5,
167
+ SendTimeout: 5
168
+ });
169
+ if (result.Code !== 1) return result;
170
+ ```
171
+
172
+ 只有纯文本时可使用打印机常见编码:
173
+
174
+ ```javascript
175
+ var result = await V8.Tcp.SendAsync({
176
+ Host: '192.168.1.88',
177
+ Port: 9100,
178
+ Text: '吾码小票\n合计:12.00\n\n',
179
+ Encoding: 'gb18030'
180
+ });
181
+ if (result.Code !== 1) return result;
182
+ ```
183
+
184
+ ### 3. 需要协议响应时显式收取
185
+
186
+ ```javascript
187
+ var result = V8.Tcp.SendAndReceive({
188
+ Host: '192.168.1.20',
189
+ Port: 4001,
190
+ Hex: '01 03 00 00 00 02 C4 0B',
191
+ ReceiveTimeout: 3,
192
+ MaxReceiveBytes: 4096
193
+ });
194
+ if (result.Code !== 1) return result;
195
+
196
+ var bytes = result.Data.RawBytes;
197
+ var hex = result.Data.Hex;
198
+ ```
199
+
200
+ 接收超时是整段接收的总时间。已收到部分数据后超时会返回成功和已有数据,并标记 `ReceiveEndReason='Timeout'`;未收到任何字节则失败。协议必须知道响应长度或结束条件,不能把超时默认当作完整帧。
201
+
202
+ ### 4. 分开判断写入、设备执行和实物结果
203
+
204
+ - `Code=1` 只表示字节已写入 TCP 连接。
205
+ - 设备协议回执可证明设备接受/处理到哪一步,但仍不一定证明机械动作完成。
206
+ - “小票已打印”需要打印机状态、设备回执或实物/硬件验收证据。
207
+
208
+ 打印不是天然幂等。不确定失败时自动重试可能重复出纸;需要可靠打印时使用业务打印单号、设备回执、状态机以及 Job/MQ/outbox,且重试策略必须识别“尚未发送”和“结果未知”。
209
+
210
+ ## 安全与运行约束
211
+
212
+ 1. TCP 不经过 `V8.Http` 的 SSRF 防护。容器网络策略/防火墙只放行目标设备网段和端口;匿名接口不得暴露任意目标。
213
+ 2. 不把票据正文、设备口令、原始帧和响应秘密写入日志;日志只记录脱敏设备标识、业务单号、字节数、耗时和错误分类。
214
+ 3. `SendAsync` 只是在当前请求内等待 I/O,不能替代后台任务。不要遗留未等待 Promise、`setTimeout` 或 `Task.Run`。
215
+ 4. 每次调用都会创建并关闭连接;不能假设设备会保持会话状态。
216
+ 5. Docker 内的 `localhost` 指容器自身。验收前从实际后端容器验证到设备 IP/端口的路由和出站策略。
217
+ 6. 载荷、超时和响应均有硬上限;不要通过拆成无限循环规避限制。
218
+
219
+ ## 修改公共能力时的同步门禁
220
+
221
+ - 源码:`Microi.Server/Microi.V8Engine/Extend/Tcp/V8Tcp.cs` 与 `V8Extend.cs`。
222
+ - 测试:真实 Jint 注入、Byte/Base64/Hex/Text、回环收发、超时/上限/非法参数。
223
+ - 编辑器:`Microi.Client/.../v8-api-server-definitions.js` 与 `Microi.Agent/src/editor/typingsManager.ts`。
224
+ - 文档与知识:官网中文后端 V8 页面、`microi.skills/README.md`、`v8-utilities` 索引、`Microi.AI/Resource` 镜像和向量资源列表。
225
+ - 验收:目标测试、后端构建、精确重启与健康检查、独立 TCP 回环;真实打印机出纸必须单独报告,不能用回环测试替代。