# 创建模块

创建可安装模块，说明模块类型、属性、方法以及纳入 Bundle 的发布验证要求。

当现有模块无法覆盖业务能力时，可以创建自定义模块。源码项目可以独立存在，但模块定义
必须在 Bundle 内创建。完成后先冻结不可变版本、更新 Bundle Manifest、创建 Bundle 版本，
再安装到目标 Runtime；完成真实调用验证后，才交给应用、智能体、技能、
后端服务或工作流使用。

## 先选择模块类型

| 类型        | 适用场景                         | 运行方式                                  | 发布前必须验证                        |
| --------- | ---------------------------- | ------------------------------------- | ------------------------------ |
| 普通业务模块    | CRM、审批、知识库、财务等由平台运行时承载的业务能力。 | 模块定义、属性、方法和用户域随 Bundle 安装物化到 Runtime。 | Bundle 能在目标工作区物化模块，方法能在运行时被调用。 |
| 外部服务接入模块  | 飞书、企微、钉钉、支付、电话通知等依赖独立服务的能力。  | 模块调用外部服务；已有租户和凭据可以通过安装配置提供。           | 外部鉴权、租户隔离、错误返回和调用链可追踪。         |
| 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 作为普通入参传入。页面、后端服务、智能体、工作流或渠道消息调用模块方法时，百积木运行时会按入口建立当前 Runtime、已验证调用方、工作区和访问用户等上下文字段。模块方法应只读取当前入口已经建立的字段，再根据模块自己的用户映射关系调用企业原有系统。

如果企业原有系统使用独立账号体系，建议在模块属性或模块业务表中保存“百积木用户 ID 与企业系统账号”的映射，例如外部员工 ID、CRM 用户 ID、门店账号或部门编码。模块方法收到调用后，先用上下文中的当前用户确认百积木侧权限，再查映射关系，最后带企业系统需要的身份凭据访问原有系统。不要让页面直接传入要代操作的外部账号，除非该方法本身就是管理员代办能力，并且已经定义了对应用户域、审计日志和权限校验。

## 使用运行时身份

模块方法只消费平台运行时已经建立的 Runtime、调用方、工作区和当前用户上下文，不自行生成、转发或解析平台内部身份凭据，也不直接调用平台内部接口。字段清单、出现条件和 HTTP 映射方式见 [模块调用上下文](/development/bundle-development/module-development/runtime-context/)。需要访问项目、数据库、托管服务等平台能力时，应使用当前 Runtime 已公开的模块方法或 SDK，并以运行时返回的方法定义为准。

> **不要把业务参数当成当前用户**
>
> 请求参数、请求体或页面自行提交的 `userId` 都是不可信业务输入。模块必须从运行时调用上下文读取当前用户；管理员代办场景需要单独的用户域、权限检查和审计，不能使用固定管理员 ID 或兼容回退。

## 定义模块方法

每个模块方法都要写清：

- 方法名称和业务说明。
- 入参字段、类型、是否必填、默认值。
- 返回结构和失败返回。
- 调用时需要的模块属性。
- 是否允许智能体、工作流、后端服务或页面直接调用。
- 是否需要幂等键、重试或超时控制。

模块方法应保持业务语义清晰，例如 `createLead`、`sendMessage`、`queryOrder`，不要暴露内部实现名。方法返回值要稳定，方便页面、智能体和工作流复用。

## 确定外部生命周期归属

模块自身不绑定整个产品的生命周期插件。模块只需要声明哪些属性用于调用外部服务，以及
这些属性是否敏感、必填或允许由安装输出写入。

如果用户已有外部账号，让用户在安装配置中填写租户编号和凭据即可。如果安装 Bundle 时
必须自动创建外部租户、数据库或许可证，再由 Bundle 顶层绑定生命周期插件。详细判断和
协议见 [高级 Bundle 开发](/development/bundle-development/advanced/)。

生命周期插件需要针对整个 Bundle 验证安装、配置变更、升级、停止和卸载，尤其要确认
卸载不会误删仍被其他 Runtime 使用的外部资源。

## 纳入 Bundle

模块不作为独立市场商品发布。先在目标 Bundle 内创建模块定义，再从项目 Git 快照冻结
不可变模块版本，由 Bundle 项目中的 `baijimu.bundle.json` 引用精确版本：

```bash
baijimu bundle module create <workspace> <bundle> \
  --project-id <projectId> \
  --name <moduleName> \
  --description <description>

baijimu bundle module version create <workspace> <bundle> <projectId> \
  --module-id <moduleId> \
  --version <semanticVersion> \
  --commit-id <commitId>
```

创建模块版本不会自动修改 Bundle 或升级已安装 Runtime。必须修改并提交
`baijimu.bundle.json`，再通过带完整 `--git-commit-id` 的 `bundle version create`、Bundle 审核和
`bundle market publish` 完成统一发布。

纳入 Bundle 前确认：

- 模块定义、方法定义和用户域已经同步到版本快照。
- 版本号语义清楚，破坏性变更使用新大版本或明确迁移说明。
- 示例属性和默认值不会覆盖用户生产配置。
- 安装到新工作区后能生成运行时可识别的服务名和业务 ID。
- Bundle 版本内容指向预期模块语义版本和安装 Artifact，不携带内部持久化身份或额外包装层。

## 安装与验证

Bundle 创建版本并纳入模块后，按真实使用路径验证：

1. 把 Bundle 安装到目标 Runtime，由 Bundle 自动安装模块。
2. 填写或确认模块属性，敏感属性只在配置页输入。
3. 打开目标运行时，确认模块安装记录、服务名和版本正确。
4. 调用每个核心方法，检查入参、返回、日志和错误信息。
5. 让页面、后端服务、智能体或工作流各完成一次实际调用。
6. 修改属性后再次调用，确认新配置已生效。

## 常见问题

| 问题        | 排查顺序                                                      |
| --------- | --------------------------------------------------------- |
| 安装后看不到模块  | 先确认工作区和 Runtime，再确认 Bundle 版本内容、模块安装记录和当前账号权限。            |
| 方法调用失败    | 先看模块属性是否完整，再看运行时日志、方法入参、外部系统返回和用户域权限。                     |
| 外部实例未创建   | 先确认 Bundle 是否声明生命周期插件，再检查插件注册、执行记录和外部服务健康状态。              |
| 属性被覆盖     | 检查默认值、Bundle 允许的插件输出、模块版本和 Bundle 升级逻辑。                   |
| 工作流调用不到模块 | 确认包含该模块的 Bundle 已安装，模块已在工作区默认 Runtime 物化，服务名和方法名与工作流节点一致。 |
