@modules.services/midway-tool
v3.6.0
Published
模块服务工具层
Readme
@modules.services/midway-tool
Tool.ms:transport、超时与负载限制
语义(重要)
**transport: 'auto'(默认)或未写**:若已配置ms.reg.*.grpc,默认按**msAutoFallback链依次尝试**grpc→h2stream→http(仅当上一层返回「传输层可重试」类结果时才进入下一层,例如 gRPC404、超时类400、负载413等;远程业务401等不会盲目降级)。未配置grpc时链退化为**['http']**。配置**ms.msAutoFallback: false**(或options.msAutoFallback)可恢复旧行为:auto 时只试 gRPC或只走 HTTP,不在协议间切换。**msAutoFallback**:true或未设时等同上述默认顺序;亦可传数组自定义顺序,如['grpc','http']跳过 h2;[]与false一样表示关闭链式降级。**transport: 'grpc'**:强制 gRPC;未配置grpc地址时返回402。**transport: 'h2stream' | 'http2'**:走 HTTP 分片并发传输(init/uploadPart/complete/abort),适合 gRPC 连接不稳定场景。**transport: 'http'**:仅 HTTP。**strictTransport**(options或ms.strictTransport):非法transport字符串时返回400;否则兼容旧行为并 warn,按 HTTP 调用。
超时(HTTP 与 gRPC 一致)
ms.reg.<name>.timeout优先,否则ms.timeout,否则 10(秒)。- HTTP:作为 axios
timeout(毫秒) =max(1000, seconds * 1000)。 - gRPC:
streamInvoke的{ timeout }+ 单次调用的 wall-clockPromise.race,与上式同一套秒数换算。
Payload 限额(先发制人,超限不发起请求)
估算基于 单次 AES2('encrypt', payload) 后的体积:
- HTTP:按
**JSON.stringify({ data: enc, keys })的字节长度**。默认限额优先读取应用合并配置中的bodyParser.jsonLimit(与@modules.services/framework等根配置里的bodyParser一致,例如'5mb'),这样Tool.ms侧不会在「已超过 bodyparser」的情况下仍发起 POST。若未配置或无法解析,则使用库内兜底 48MB。仍可通过ms.msHttpMaxPayloadBytes/options.msHttpMaxPayloadBytes显式覆盖。 - gRPC:按 密文字符串 UTF-8 字节数(流式正文合计);默认 62MB,对齐客户端
grpc.max_*_message_length64MB。可配ms.msGrpcMaxPayloadBytes、msGrpcMaxPayloadBytes。
超限返回 **code: 413**,message 中含估算字节数、限额与分批建议。
gRPC 重试(可选)
**msGrpcMaxRetries**:在ms或options上配置,表示 额外重试次数(不含首次),默认 0。**msGrpcRetryableStatusCodes**:默认可重试:UNAVAILABLE、DEADLINE_EXCEEDED、RESOURCE_EXHAUSTED。- 重试前会 invalidate 对应
grpcUrl的 stub 缓存,便于下次重建连接。 - 失败日志为一行结构化 JSON:
event=ms_grpc_failure、target、queryMs、grpcCode、attempt、willRetry等。
单帧大小
msGrpcChunkSize(或chunkSize)不得超过**msGrpcMaxMessageBytes**(可选;未设则按 64MB 客户端上限校验)。
HTTP2 分片并发(h2stream)
- 接口:
/__MicroService__/h2/init、/__MicroService__/h2/uploadPart、/__MicroService__/h2/complete、/__MicroService__/h2/abort。 - 字段:
uploadId、partNo、totalParts、checksum(sha256)、idempotencyKey、expiresAt。 - 服务端落盘目录:
$TMPDIR/midway-ms-h2/<uploadId>/(分片文件 +meta.json),避免“整包全内存拼接”。 - 客户端配置:
msH2PartSize、msH2Concurrency、msH2SessionTTLSeconds。 - 单片重试:
msH2PartMaxRetries、msH2PartRetryBaseMs(指数退避,失败片可独立恢复)。 - complete 补片闭环:客户端 complete 前本地自检缺片;若服务端 complete 返回
missingParts,客户端按清单补传后自动重试 complete(msH2CompleteMissingRetryRounds控制轮次)。 - 存储模式:
msH2Store = 'disk' | 'objectStorage'(当前objectStorage为本地仿真语义,已预留后续接 OSS SDK)。
微服务名类型同步(类 eps / 远端 manifest → 本地补全)
目标:this.tool.ms('module.method') 在 IDE 中有字面量补全。TypeScript 只认磁盘上的类型,因此采用「提供方 HTTP 暴露清单 → 调用方写成 .d.ts(随 Tool 包启动自动同步或 CLI)→ declare module 合并 MicroServiceNameMap」。
提供方(本库已内置)
- 在
ms.offer中注册 class 后启动应用,与现有__MicroService__同条件挂载 Controller。 - GET
/<prefix>/registry-manifest(与__MicroService__一致时为**GET /__MicroService__/registry-manifest**) - 返回 JSON(与
Tool.ms加解密无关,无需x-ms-k):
{
"code": 200,
"message": "ok",
"data": {
"schemaVersion": 1,
"manifestVersion": "<sha256 hex,64 字符,由当前全量可调服务名动态计算>",
"generatedAt": "2026-05-04T12:00:00.000Z",
"total": 2,
"names": ["user.getDetail", "user.getList"],
"modules": { "user": ["getDetail", "getList"] }
}
}- 版本(与 offer 内容绑定):无需手写版本号。
manifestVersion为当前扫描得到的全量module.method名称列表(排序后逐行拼接)的 SHA256 十六进制(64 字符);只要ms.offer里可调用的方法名增删,hash 即变化。仅修改方法实现而不改方法名时,hash 不变(与“仅按对外接口名做类型补全”一致)。调用方***.sync.json** 中保存同一字符串,与提供方一致即视为无需再拉。 - 省流探针:
**GET ...?metaOnly=1**(可再带key)返回{ schemaVersion, manifestVersion, generatedAt, total, paramsByNameCount },无names;调用方将paramsByNameCount与本地.sync.json内同名字段比对,与manifestVersion一并用于判断是否跳过全量拉取(避免版本号相同但 params 层刚就绪时仍跳过 typings 更新)。 - 分页(协议能力,非手工流程):接口支持
**limit** /**offset,供 sync-ms-registry-dts 脚本在内部自动拼接;业务侧不需要自己循环拉取。返回中带**total、**hasMore**、**nextOffset**及当前页**modules**。单次**limit**受**ms.msRegistryPageMax**(默认 10000)封顶。- 不传
limit:一次响应中带全量names(小清单或调试方便);大批量场景请只使用下方同步脚本,由脚本自动分页,避免单次响应体过大。
- 不传
- 清单由
**ms.offer的 class 上 public 方法** 扫描得到(与user.*列表逻辑一致);new失败时回退为仅读prototype。 - 可选安全:配置
**ms.msRegistryManifestKey** 后,须带查询参数**?key=<同值>** 或请求头**x-ms-registry-key**。生产环境建议开启或内网限制访问。 - 关闭接口:
ms.msRegistryManifestEnabled: false(返回 404)。 - 网关/Nginx 可对
GET …/registry-manifest开启 gzip,进一步减小传输体积。
调用方(业务工程)
底层仍是:把远端清单写成仓库里的 .d.ts(TypeScript 不能凭空从网上推断类型)。分页、metaOnly、hash 比对均由库内 **runMsRegistryClientSync** 统一完成,无需手写 limit/offset 循环。
方式一(推荐):进程启动时自动同步——日常无需敲命令
tsconfig.json的include包含生成目录(如typings/**/*.d.ts)。- 调用方
**imports仅需引入@modules.services/midway-tool(与现有做法一致)**,无需单独挂载其它 Configuration。 - 在
**ms** 块中配置**ms.reg**(调用方已有)与**ms.msRegistryClientSync**。清单地址由ms.reg某一条 推导:host优先(支持http(s)://或裸host:port);若无可用host则用同条的grpc(host:port或仅主机名,未写端口时与 Tool 一致默认 :55555),再在前缀补上http://或https://(端口为 443 时),最后拼接**/__MicroService__/registry-manifest**。一般只需ms.reg里一项 +msRegistryClientSync.enabled: true。
ms:
reg:
platform:
enable: true
host: http://127.0.0.1:7001 # 或与 grpc 二选一 / 互补(见上文)
mk: your-mk-if-needed
msRegistryClientSync:
enabled: true多条 ms.reg 时默认按 key 字典序用第一条能解析出地址的条目;极少场景可用嵌套配置里的 regKey 或环境变量 MS_REGISTRY_SYNC_FROM_REG 指定键名。
可选覆盖:顶层 **msRegistryClientSync.registryManifestUrl**(或与 **ms.msRegistryClientSync** 合并,嵌套优先),或环境变量 **MS_REGISTRY_SYNC_URL**。
应用 **npm run dev / npm start** 且提供方可达时,ToolConfiguration 在 onReady 自动拉清单并写入默认路径 **typings/microServiceNames.generated.d.ts** 与同目录 ***.sync.json**。
- 生产环境:默认
**NODE_ENV=production** 时不自动写 typings(避免多副本重复请求);需要时在**ms.msRegistryClientSync**设**skipInProduction: false**。 - 仅用环境变量:
MS_REGISTRY_AUTO_SYNC=1,且**MS_REGISTRY_SYNC_URL**;或与**ms.reg**联用 **MS_REGISTRY_SYNC_FROM_REG=<键名>**。 - 密钥:清单请求参数
**key** 优先顺序为**ms.msRegistryClientSync.key**→ 所选**ms.reg条目的mk**→**MS_REGISTRY_KEY**(与提供方**ms.msRegistryManifestKey**对齐时使用)。 - IDE:生成/更新
.d.ts后若补全未刷新,可「TypeScript:重启 TS 服务」或重载窗口。
方式二:CLI(CI、或与启动解耦时使用)
先 **npm run build** 本包(CLI 依赖 **dist/lib/msRegistryClientSync.js**),再:
pnpm exec sync-ms-registry-dts -- --url http://127.0.0.1:7001/__MicroService__/registry-manifest --out typings/microServiceNames.generated.d.ts逻辑与方式一相同;支持 **--input 本地 JSON**、--force、MS_REGISTRY_PAGE_SIZE 等。
角色对照
| 角色 | 是否要手动敲同步命令 | | --------- | ------------------------- | | 服务提供方 | 否,启动即有清单接口。 | | 调用方 | 否(若已用方式一);否则用方式二或 CI。 |
超大清单:几千条名称会产生较大的 .d.ts,一般 IDE 可承受。
合并后的声明作用于 **MicroServiceNameMap**,Tool.ms 首参可收窄;建议将 **typings/*.d.ts** 纳入版本库或与 CI 约定生成时机。
清单还可携带 paramsByName(各 module.method 对应 opt.params 的 TS 对象字面量)。同步后会写入调用方 typings/*.generated.d.ts 中的 MicroServiceMethodParams,Tool.ms 第二参随方法名收窄。
提供方如何生成 paramsByName(无需业务配置):在进程工作目录下若存在 tsconfig.json 与 源码,库会用 ts-morph 扫描 ms.offer 中各类的首参类型并解析 params 字段,自动填入清单。若启动时 process.cwd() 不在项目根(例如在 dist/、PM2 子目录),请设置环境变量 MS_REGISTRY_SCAN_ROOT 指向含 tsconfig.json 与源码的根目录,否则会解析不到类定义,paramsByName 为空。若部署环境 仅有 dist/、无 .ts,请在构建阶段生成 registry-manifest-params.json(或 dist/registry-manifest-params.json),格式为 { "module.method": "{ key?: type }" } 或 { "paramsByName": { ... } };进程启动时会自动合并进清单(亦可通过 MS_REGISTRY_PARAMS_FILE 指定路径)。合并优先级:源码解析 优先于 缓存文件 优先于 Function#toString 兜底。
调用方排查(没有生成 typings / 提供方看不到清单请求)
- 是否开启同步:必须
ms.msRegistryClientSync.enabled: true(或MS_REGISTRY_AUTO_SYNC=1);仅有ms.reg不会自动写 typings。键名须为enabled(库内已将误写的enable映射为enabled)。 - 配置是否被读到:同步读的是 全局合并配置(与
Tool.ms运行时一致)。若曾写在其它层级导致未合并,请确认业务的config.default/ 环境配置 里ms与ms.msRegistryClientSync确实加载(启动日志里应出现[MsRegistryClientSync]成功、跳过版本、或明确 warn)。 - 生产环境:默认
NODE_ENV=production跳过写盘;本地开发勿误设 production,或设skipInProduction: false。 - 输出路径:默认在进程
cwd下typings/microServiceNames.generated.d.ts;若从非项目根目录启动(部分 PM2/脚本),文件会出现在cwd/typings,用全局搜索microServiceNames.generated定位;或通过ms.msRegistryClientSync.out指绝对路径。 - 清单 URL:解析失败时日志为
未配置清单 URL...,请检查ms.reg的host/grpc或显式registryManifestUrl。 - 生成的
typings/*.generated.d.ts:须在declare module '@modules.services/midway-tool'之前包含import '@modules.services/midway-tool';(或export {};)。若为纯declare module,会 遮蔽 包导出并触发 TS2305/TS2322。同步写入前库内会再跑一次ensureMsRegistryTypingsAugmentsPackage,自动补上import(仍建议升级本包,勿依赖旧 CLI)。 - (备选)若
imports: [tool]仍报 TS2322:改用imports: [midwayToolImports](与import { midwayToolImports } from '@modules.services/midway-tool'或tool.midwayToolImports配合),与 Midway 期望的{ Configuration }形态完全一致。
手写补充
若个别名字未进 manifest,可在业务里再建一个 typings/ms-handwritten.d.ts 合并同一块 interface MicroServiceNameMap;补 params 时合并同一模块内的 interface MicroServiceMethodParams。
