创建模块
创建可安装模块,说明模块类型、属性、方法以及纳入 Bundle 的发布验证要求。
当现有模块无法覆盖业务能力时,可以创建自定义模块。源码项目可以独立存在,但模块定义 必须在 Bundle 内创建。完成后先冻结不可变版本、更新 Bundle Manifest、发布 Bundle, 再安装到目标 Runtime;完成真实调用验证后,才交给应用、智能体、技能、 后端服务或工作流使用。
先选择模块类型
| 类型 | 适用场景 | 运行方式 | 发布前必须验证 |
|---|---|---|---|
| 普通业务模块 | CRM、审批、知识库、财务等由平台运行时承载的业务能力。 | 模块定义、属性、方法和用户域随应用运行时部署。 | 模块能安装到目标工作区,方法能在运行时被调用。 |
| 外部服务接入模块 | 飞书、企微、钉钉、支付、电话通知等依赖独立服务的能力。 | 模块调用外部服务;已有租户和凭据可以通过安装配置提供。 | 外部鉴权、租户隔离、错误返回和调用链可追踪。 |
| HTTP 代理模块 | 已有稳定 HTTP 服务,只需要把接口包装成模块方法。 | 模块方法把入参映射为 HTTP 请求,返回结果写回调用方。 | 请求 URL、鉴权头、超时、错误返回和日志可追踪。 |
| 渠道模块 | 微信、飞书、企微、钉钉、Webhook 等入口接入。 | 模块保存渠道凭据和回调配置,运行时负责消息接入与事件分发。 | 回调地址可访问,消息能进入正确工作区,事件链路有日志。 |
先定责任边界
如果模块依赖外部系统,先明确哪些状态由百积木保存,哪些状态由外部服务保存。不要把长期 token、租户密钥或外部实例 ID 写进页面文案或前端代码。
创建前准备
创建模块前,先确认这些信息:
- 模块所属工作区和项目。
- 模块名称、描述、适用用户和业务边界。
- 需要保存的属性,例如 token、租户 ID、回调地址、默认用户域、外部实例 ID。
- 要暴露的方法清单、每个方法的入参、出参、错误语义和调用权限。
- 整个 Bundle 是否需要自动创建、迁移或回收平台外部资源。
- 模块要纳入哪个 Bundle,以及模块版本和 Bundle 升级策略。
定义模块属性
模块属性用于保存安装后可配置或由服务端回写的值。属性定义需要写清字段名、类型、是否必填、默认值、是否敏感、是否由外部托管服务管理。
常见属性:
| 属性 | 用途 |
|---|---|
serviceBaseUrl | 外部服务或代理服务的业务 API 地址。 |
tenantId | 外部系统租户标识。 |
accessToken | 外部系统访问令牌,应标记为敏感属性。 |
externalInstanceId | Bundle 生命周期插件创建外部资源时返回的安装实例主键。 |
status | 外部实例或模块配置状态。 |
敏感属性只用于运行时调用,不应被页面、日志或普通接口明文展示。用户已有的租户编号可以 作为安装配置填写;自动创建的外部实例标识应由 Bundle 生命周期插件输出,再由 Bundle Service 校验并写入目标资源属性。
定义用户域
用户域用于说明哪些用户可以调用哪些方法。创建模块时至少定义默认用户域,例如 authenticated,再按业务需要拆分管理员、普通成员、外部客户等域。
设计用户域时,重点确认:
- 普通用户能否新增、编辑、删除业务数据。
- 管理员是否拥有配置、审核和导出权限。
- 智能体或工作流调用模块方法时使用哪个用户身份。
- 外部渠道消息进入后映射到哪个用户域。
获取当前访问用户
专用模块通常不需要自己实现登录态,也不应依赖前端把 userId、手机号或外部账号 ID 作为普通入参传入。页面、后端服务、智能体、工作流或渠道消息调用模块方法时,百积木运行时会把当前工作区、项目、访问用户、用户域和调用来源放在调用上下文中,模块方法应从上下文读取当前用户,再根据模块自己的用户映射关系调用企业原有系统。
如果企业原有系统使用独立账号体系,建议在模块属性或模块业务表中保存“百积木用户 ID 与企业系统账号”的映射,例如外部员工 ID、CRM 用户 ID、门店账号或部门编码。模块方法收到调用后,先用上下文中的当前用户确认百积木侧权限,再查映射关系,最后带企业系统需要的身份凭据访问原有系统。不要让页面直接传入要代操作的外部账号,除非该方法本身就是管理员代办能力,并且已经定义了对应用户域、审计日志和权限校验。
系统模块访问平台服务时的身份头
系统模块通过 /gateway-module/** 反向访问项目、数据库、托管服务等平台能力时,使用两种相互独立的身份:
- workspace module token 证明“哪个已安装模块、哪个工作区正在调用”。
- gateway actor 证明“这次调用代表哪个当前用户”。module token 本身不包含当前用户。
模块方法不应声明、生成、转发或覆盖平台身份头。X-Baijimu-*、X-Gateway-Module-* 和 X-Partner-* 都是平台保留命名空间;即使模块设置了这些请求头,gateway2 和 OpenResty 也会在信任边界清除。OpenResty 的普通公网代理入口会无条件删除这三个命名空间;必须先用于验签的 /gateway-module/** 和 /partner/v1/** 会在验签完成后删除,再由平台重建允许向后端传递的头。
gateway2 发往 OpenResty 的内部验签请求包含:
| 请求头 | 含义 |
|---|---|
Authorization: Bearer lc_mtok_... | 当前安装模块的 workspace module token。 |
X-Baijimu-Gateway-Actor-UserId | 从受信运行时上下文取得的当前用户 ID。 |
X-Baijimu-Gateway-Actor-WorkspaceId | 当前运行时工作区 ID。 |
X-Baijimu-Gateway-Actor-Timestamp | actor 断言生成时间。 |
X-Baijimu-Gateway-Actor-Nonce | 让每次断言保持唯一的随机值。 |
X-Baijimu-Gateway-Actor-Signature | 绑定用户、工作区、module token、HTTP 方法和请求 URI 的 HMAC 签名。 |
OpenResty 校验成功后会删除 module token、时间戳、nonce、签名以及调用者提供的三个平台保留命名空间,只向后端服务重建以下可信上下文:
| 请求头 | 含义 |
|---|---|
X-Baijimu-Gateway-Actor-UserId | 已验签的当前用户 ID;用户权限判断只能使用这个值。 |
X-Baijimu-Gateway-Actor-WorkspaceId | 已验签且与 module token 一致的工作区 ID。 |
X-Gateway-Module-WorkspaceId | 已验证 module token 所属的工作区。 |
X-Gateway-Module-TokenId | 已验证 module token 的内部记录 ID。 |
X-Gateway-Module-ModuleId | module token 绑定的模块 ID,未绑定时为空。 |
X-Gateway-Module-ServiceId | module token 绑定的服务 ID,未绑定时为空。 |
只有需要“当前操作者”的接口才会收到两个 X-Baijimu-Gateway-Actor-* 头;只校验模块和工作区的接口仅收到四个 X-Gateway-Module-* 头。普通 Web 请求不会收到 X-Baijimu-*。
CLI 等外部调用者使用 PAT 访问 /partner/v1/** 时,OpenResty 验证 PAT 后向后端提供:
| 请求头 | 含义 |
|---|---|
X-Partner-UserId | PAT 所属用户 ID。 |
X-Partner-Scopes | PAT 的已验证权限范围。 |
X-Partner-WorkspaceId | 工作区范围接口解析出的工作区 ID;非工作区接口不提供。 |
userId | 现有 PAT 后端兼容头,值由 OpenResty 覆盖;新服务应读取 X-Partner-UserId。 |
workspaceId | 仅部分现有工作区接口使用的兼容头;新服务应读取 X-Partner-WorkspaceId。 |
PAT 请求不会获得 gateway actor 身份,不能自行构造 X-Baijimu-Gateway-Actor-* 或 X-Gateway-Module-*。Authorization 在凭证校验后不会转发给业务后端。
不要把普通 userId 当成操作者身份
模块参数、请求体或普通 userId 请求头都属于业务输入,可能由调用者控制。平台后端在处理 /gateway-module/** 请求时,只能使用验签后的 X-Baijimu-Gateway-Actor-UserId 判断当前操作者,不能回落到普通 userId,也不能使用 userId=1 作为超级管理员兼容值。
定义模块方法
每个模块方法都要写清:
- 方法名称和业务说明。
- 入参字段、类型、是否必填、默认值。
- 返回结构和失败返回。
- 调用时需要的模块属性。
- 是否允许智能体、工作流、后端服务或页面直接调用。
- 是否需要幂等键、重试或超时控制。
模块方法应保持业务语义清晰,例如 createLead、sendMessage、queryOrder,不要暴露内部实现名。方法返回值要稳定,方便页面、智能体和工作流复用。
确定外部生命周期归属
模块自身不绑定整个产品的生命周期插件。模块只需要声明哪些属性用于调用外部服务,以及 这些属性是否敏感、必填或允许由安装输出写入。
如果用户已有外部账号,让用户在安装配置中填写租户编号和凭据即可。如果安装 Bundle 时 必须自动创建外部租户、数据库或许可证,再由 Bundle 顶层绑定生命周期插件。详细判断和 协议见 高级 Bundle 开发。
生命周期插件需要针对整个 Bundle 验证安装、配置变更、升级、停止和卸载,尤其要确认 卸载不会误删仍被其他 Runtime 使用的外部资源。
纳入 Bundle
模块不作为独立市场商品发布。先在目标 Bundle 内创建模块定义,再从项目 Git 快照冻结 不可变模块版本,由 Bundle Manifest 引用精确版本:
baijimu bundle module create <workspace> <bundle> \
--project-id <projectId> \
--name <moduleName> \
--description <description>
baijimu bundle module freeze <workspace> <bundle> <projectId> \
--module-id <moduleId> \
--version <semanticVersion> \
--commit-id <commitId>冻结模块版本不会自动修改 Bundle 或升级已安装 Runtime。必须更新 Manifest,并通过
bundle version publish、Bundle 审核和 bundle market publish 完成统一发布。
纳入 Bundle 前确认:
- 模块定义、方法定义和用户域已经同步到版本快照。
- 版本号语义清楚,破坏性变更使用新大版本或明确迁移说明。
- 示例属性和默认值不会覆盖用户生产配置。
- 安装到新工作区后能生成运行时可识别的服务名和业务 ID。
- Bundle 版本的
resolvedLock指向预期模块版本和安装 Artifact。
安装与验证
Bundle 发布并把模块纳入资源锁后,按真实使用路径验证:
- 把 Bundle 安装到目标 Runtime,由 Bundle 自动安装模块。
- 填写或确认模块属性,敏感属性只在配置页输入。
- 打开目标运行时,确认模块安装记录、服务名和版本正确。
- 调用每个核心方法,检查入参、返回、日志和错误信息。
- 让页面、后端服务、智能体或工作流各完成一次实际调用。
- 修改属性后再次调用,确认新配置已生效。
常见问题
| 问题 | 排查顺序 |
|---|---|
| 安装后看不到模块 | 先确认工作区和 Runtime,再确认 Bundle 资源锁、模块安装记录和当前账号权限。 |
| 方法调用失败 | 先看模块属性是否完整,再看运行时日志、方法入参、外部系统返回和用户域权限。 |
| 外部实例未创建 | 先确认 Bundle 是否声明生命周期插件,再检查插件注册、执行记录和外部服务健康状态。 |
| 属性被覆盖 | 检查默认值、Bundle 允许的插件输出、模块版本和 Bundle 升级逻辑。 |
| 工作流调用不到模块 | 确认模块已安装到工作区默认运行时,服务名和方法名与工作流节点一致。 |