# App Gateway 接入与配置

为复用百积木用户与工作区体系的平台应用选择 App Gateway，并完成入口、授权、能力、调用和版本配置。

App Gateway 是平台应用复用百积木登录用户、工作区安装态和权限体系的标准业务入口。
只要应用由 Bundle 安装到工作区，并且页面代表当前平台用户调用该安装可用的能力，就应
使用 App Gateway。

团队开发前端并从百积木工作区打开、复用平台身份时，应创建独立的 Platform Application，
使用 `ENVIRONMENT_HOSTED_APP` 由目标环境托管，通过同源配置、网页授权和 PKCE 获取 `appClientToken`。
历史 `TRUSTED_EXTERNAL_APP` 的启动和平台授权已被拒绝；外部自托管站点应使用自己的后台授权。

## 选择平台应用入口

| `entryType`                  | 适用场景                  | App Gateway 接入方式                         |
| ---------------------------- | --------------------- | ---------------------------------------- |
| `PLATFORM_ROUTE`             | 平台管理端已经内置的页面          | 继承平台管理端当前登录态，不执行外部应用 PKCE                |
| `ENVIRONMENT_HOSTED_APP`     | 由当前环境托管、复用平台用户和工作区的前端 | 读取同源配置，通过网页授权 + PKCE 获取 `appClientToken` |
| `TRUSTED_EXTERNAL_APP`（历史类型） | 待迁移的外部前端              | 当前启动和授权链路拒绝，须创建环境托管应用新版本                 |
| `EXTERNAL_URL`               | 不访问平台能力的普通链接          | 不接入 App Gateway                          |

> **App Gateway 应用凭证**
>
> 环境托管平台应用调用 App Gateway 时使用 `appClientToken`。该 token 由网页
> 授权码兑换流程签发，并绑定当前用户、工作区、默认 Runtime 应用、平台应用安装和
> 授权范围。

## 调用链

```text
百积木平台用户
  -> 从工作区打开平台应用 accessUrl
  -> 网页授权页校验登录态、工作区访问权和安装记录
  -> PKCE 授权码兑换 appClientToken
  -> 浏览器请求公开前缀 /app-gateway/api
  -> App Gateway 校验结构化 target、方法、用户、安装和版本
  -> 按安装记录把模块领域引用 或 interface 解析为 Runtime businessId
  -> 校验工作区方法权限并调用绑定的模块方法
  -> 返回统一响应
```

应用前端只依赖公开、稳定的 App Gateway 契约。公共 SaaS 当前前缀是
`https://api.baijimu.com/app-gateway/api`，但它不是所有部署的默认值；实际前缀必须由当前环境的
部署配置注入。不要在业务代码中硬编码平台内部服务、网关地址、单个工作负载 IP 或端口。

App Gateway 负责完整的应用调用边界：

- 确认“谁在调用、属于哪个安装、当前版本允许调用什么 target 和方法”。
- 固定 module target 按安装账解析；interface target 按当前安装的 `interfaceBindings` 解析成具体
  Runtime `businessId`。
- 使用平台维护的安装态调用身份向目标 Runtime 网关发起调用，并传递已验证的当前用户上下文。

前端不得读取、缓存或提交具体 `businessId`，也不自行拼接内部调用信封。
随 Bundle 安装的平台应用必须在 `methodDefinitions` 声明 `module | interface` target 和精确方法；
安装器负责校验目标 Module 只能由当前 Bundle 或其依赖 Bundle 提供，并生成运行态绑定。

## 配置一：创建独立平台应用

每个独立的平台应用都应有稳定的 `platformAppId` 和独立的平台项目。不要把前端
页面仅作为 Bundle 中一个匿名 URL；否则平台无法为它管理版本、Bundle 安装边界和能力声明。

由环境托管且复用平台身份的前端在专属 `PLATFORM_APPLICATION` 项目的 `baijimu.platform-application.json` 中使用：

```json
{
  "schemaVersion": "3.0.0",
  "entry": {
    "type": "ENVIRONMENT_HOSTED_APP",
    "urlTemplate": "/index.html",
    "openMode": "WORKSPACE_TAB"
  },
  "manifest": {}
}
```

`platformAppId` 是项目与 Bundle 资源的一对一绑定身份，不在配置文件中重复声明。

环境托管入口是版本内相对根路径，不能包含外部 URL、查询串、hash 或 `{installId}`。
平台解析实际部署地址并在启动时追加 `installId`。`workspaceId`、`userId`、`appClientToken`、业务密钥
和内部服务地址都不能放进入口 URL。

如果页面本来就是平台管理端源码中的内置路由，才使用：

```json
{
  "schemaVersion": "3.0.0",
  "entry": {
    "type": "PLATFORM_ROUTE",
    "urlTemplate": "/workspace/example",
    "openMode": "CURRENT"
  },
  "manifest": {}
}
```

使用 `PLATFORM_ROUTE` 前必须确认这个精确相对路径已经由平台管理端源码注册并随平台版本上线。
`/workspace/{workspaceId}/platform-app/{platformAppId}` 是通用平台应用宿主地址，不会自动加载项目里的 React 构建产物，不能作为独立 Bundle 应用的默认路由。

完整的入口选择规则见
[入口类型与应用清单](/development/bundle-development/platform-application-development/entry-and-manifest/)。

## 配置二：声明最小能力

平台应用项目配置必须在 `methodDefinitions` 中声明 target 和模块方法。固定依赖直接声明 module：

```json
[
  {
    "target": {
      "type": "module",
      "bundleId": "wechat-connector",
      "moduleId": "wechat-official-account"
    },
    "methods": [
      "authorizationUrl",
      "listAuthorizedAccounts",
      "createDraft"
    ]
  }
]
```

如果产品要求允许不同 Module 替换实现，才把 target 改为 interface：

```json
{
  "methodDefinitions": [
    {
      "target": {
        "type": "interface",
        "name": "wechat-official-account"
      },
      "methods": [
        "authorizationUrl",
        "listAuthorizedAccounts",
        "createDraft"
      ]
    }
  ],
  "manifest": {
    "interfaceBindings": {
      "wechat-official-account": {
        "bundleId": "wechat-connector",
        "moduleId": "wechat-official-account"
      }
    }
  }
}
```

安装器根据模块领域引用 解析当前 Runtime 的具体 service。前端发送与版本声明一致的结构化 target；
解析结果和调用身份只存在于安装态，不能由前端选择、改写或持久化。

不要声明通配权限，也不要把未来可能使用的管理方法提前加入当前版本。平台应用不能用
`methodDefinitions` 代替平台内部 HTTP API 授权；它只能声明已安装 Runtime 模块的方法。

能力声明、已启用的精确 Bundle 安装和当前用户的工作区/方法权限同时生效；其中任何一层不满足，App Gateway 都应拒绝
调用。字段规则见
[声明应用能力](/development/bundle-development/platform-application-development/capabilities/)。

## 配置三：使用网页授权 + PKCE 取得应用身份

`ENVIRONMENT_HOSTED_APP` 使用 `@baijimu/platform-application-sdk` 从 URL 读取 `installId`，生成 PKCE
会话并跳转当前部署配置提供的统一授权页。公共 SaaS 的授权页地址只是一个部署示例：

```text
https://console.baijimu.com/#/platform-app-authorize
  ?installId=pia_xxx
  &redirectUri=https%3A%2F%2Fapp.example.com%2F
  &codeChallenge=...
  &codeChallengeMethod=S256
  &state=...
```

平台网页授权页校验登录用户、工作区权限、精确 Bundle 应用安装和版本能力范围，然后只向同源
`redirectUri` 回传一次性 `code`。SDK 校验 `state` 后执行以下兑换：

```http
POST https://api.baijimu.com/app-gateway/api/workspace-runtime/apps/{installId}/token/exchange
Content-Type: application/json

{
  "code": "authorization-code",
  "codeVerifier": "original-code-verifier"
}
```

返回的 `appClientToken` 同时绑定用户、工作区、默认 Runtime 应用、平台应用安装和
授权范围，不能跨 Runtime 或跨安装复用。完整流程见
[平台应用授权与 PKCE](/development/bundle-development/platform-application-development/authorization/)。

## 配置四：统一公开 API 前缀

安装标准 SDK：

```bash
pnpm add --save-exact @baijimu/platform-application-sdk@7.0.0
```

应用启动层从同源环境配置创建唯一客户端，缺少配置时直接失败：

```ts
import { createPlatformApplicationClientFromHostedConfiguration } from "@baijimu/platform-application-sdk";

const client = await createPlatformApplicationClientFromHostedConfiguration(applicationDefinition.id);

await client.bootstrap();
```

SDK 从 `/.well-known/baijimu-platform-application.json` 读取当前应用 origin、
App Gateway 与授权地址。该配置由平台托管流程提供，不能被前端归档覆盖，也不接受来自查询参数的替代地址。

SDK 使用的标准入口如下：

| 用途       | 请求                                                        |
| -------- | --------------------------------------------------------- |
| 授权码兑换    | `POST /workspace-runtime/apps/{installId}/token/exchange` |
| 撤销 token | `POST /workspace-runtime/apps/{installId}/token/revoke`   |
| 调用模块方法   | `POST /workspace-runtime/apps/{installId}/invoke`         |

除 token exchange 外，调用都携带：

```http
Authorization: Bearer {appClientToken}
```

业务组件通过客户端调用方法，第三个参数直接作为方法请求体：

```ts
const result = await client.call(
  {
    type: "module",
    bundleId: "wechat-connector",
    moduleId: "wechat-official-account",
  },
  "listAuthorizedAccounts",
  { offset: 0, limit: 50 },
);
```

完整请求和响应协议见
[调用模块方法](/development/bundle-development/platform-application-development/module-method-calls/)，
启动状态机见
[Web 接入与运行态调用](/development/bundle-development/platform-application-development/sdk-and-runtime/)。

## 版本创建与安装

环境托管前端从专属项目主线按精确版本构建，项目根目录 `package.json` 版本必须一致。
CLI 等待构建完成后创建不可变平台应用版本，再由 Bundle 引用精确版本；完整命令见
[创建版本并纳入 Bundle](/development/bundle-development/platform-application-development/create-and-install/)。

使用 `bundle manifest include <bundle> platform-application` 选择目录中的应用与精确版本，CLI 自动写入 `definition.platformApplications`。安装或升级后，平台自动提供托管入口和同源配置，无需开发者另行创建或部署静态站点。

修改前端源码、入口或方法声明后，都需要创建新的项目与平台应用版本，由新的 Bundle 版本引用并升级目标工作区。
已有不可变版本不能改绑提交或替换构建产物；只创建平台应用版本不会自动升级已经安装的 Bundle 资源。

安装或升级预览会汇总平台应用需要调用的、由其他依赖 Bundle 提供的精确方法。只要清单
非空，Manager 和 CLI 都要求安装用户确认，并把同一份结构化清单回传；后端会重新解析并
逐项比对，不能通过修改请求扩大范围。同一 Bundle 内的方法不会重复提示，但仍受相同的
运行态调用身份、版本声明和当前用户权限约束。

## 验收清单

1. 平台应用有稳定 `platformAppId` 和独立项目。
2. 前端使用 `ENVIRONMENT_HOSTED_APP` 和版本内入口，启动地址由平台追加 `installId`，同源配置校验通过。
3. 网页授权回跳只携带一次性授权码，不在 URL 中传 token。
4. token exchange 返回的工作区、`runtimeAppId` 和安装信息与当前入口一致。
5. 前端只通过公开 `/app-gateway/api` 前缀访问平台能力。
6. 前端通过标准 SDK 调用平台应用版本声明的 `module | interface` target 和 `method`。
7. Bundle 的资源依赖或接口绑定与安装态运行服务一致，且平台应用版本已精确声明对应方法。
8. 已声明的方法可调用，未声明的方法被拒绝。
9. 不同用户、工作区和安装之间不会复用 token。
10. `appClientToken` 不出现在入口 URL、日志、埋点或错误上报中。
11. 业务系统密钥不进入平台应用前端。
12. 新平台应用版本已被 Bundle 精确引用，并已升级到目标工作区。
13. 跨 Bundle 方法清单已经由安装用户确认，未确认或被篡改的请求会被拒绝。

排障时先判断失败发生在入口、网页授权、token exchange、App Gateway 权限校验、
运行态解析还是目标模块执行。不要因为某一层配置错误就绕开 App Gateway 的安装和授权
校验；应在实际失败层修复配置或实现。
