# External API (`xlyApi`)

`xlyApi` is the API surface external integrators consume. It runs as
its own Spring Boot WAR (entry class
`xlyApi/src/main/java/com/xly/ApiApplicationBoot.java`) at context-path
`/xlyApi`, with its own `application-*.yml` files.

The defining design choice: **most external endpoints are not hard-coded
in Java — they are rows in `sysapi`**. New external APIs are *registered
as data*, like the rest of xly. The runtime's job is to look up the
`sApiCode`, validate the caller, and execute whatever the metadata says.

## The data-driven dispatcher — `/api/invoke/{sApiCode}`

The single most important endpoint:

```
POST /xlyApi/api/invoke/{sApiCode}
Authorization: Bearer <token>      (or query param ?authorizationt=<token>)
Content-Type: application/json
{ ...request body, passed through as `sBody` to the handler... }
```

Handler: `ApiController.invoke()` at
`xlyApi/src/main/java/com/xly/api/web/ApiController.java:223`. The mapping
is `@RequestMapping` (method-agnostic at the framework level); the per-API
`sysapi.sMethod` column declares the expected verb for callers, and
`ApiCheckUtil` enforces it at dispatch.

The flow:

1. Read the `Authorization` header (fallback: `authorizationt` query
   parameter).
2. Look up the `sysapi` row keyed by `sApiCode` (via
   `ApiServiceImpl.invoke` →
   `SELECT … FROM sysapi WHERE sApiCode = #{sApiCode}`).
3. If the row's `bHasToken` flag is set, AES-decrypt the bearer token to
   recover the `corpid`, then validate that `corpid` against `sysapibrand`
   via `BrandServiceImpl.selectByCorpid`. If the brand row's `iLossTime`
   is non-zero, also check the token's embedded timestamp hasn't expired.
   (`sysapithirdtoken` is for *outbound* tokens — xly calling third-party
   APIs — not for validating inbound bearer tokens here.)
4. Run the SQL template stored in `sysapi.sDataSql` with the request
   body merged into the parameter map.
5. Log the call to `sysapilog`.
6. Return the result wrapped in `AjaxResult`.

So adding a new external API is an admin/PM task: insert a row into
`sysapi`, supply the SQL template, set the flags, give the integrator
the `sApiCode` and a token. No Java code change.

### `sysapi` columns of interest

| Column | Meaning |
|---|---|
| `sApiCode` | The path variable consumers send. Must be unique per tenant. |
| `sApiName` | Human label. |
| `sApiUrl` / `sApiUrlRef` | Computed URL — `sApiUrlRef + sApiCode` — for outbound dispatches. |
| `sMethod` | The HTTP method this API expects (`GET`, `POST`, …). |
| `sHeader`, `sBody`, `sBodyNode`, `sBodyType` | Templates describing the inbound request shape. |
| `sDataSql` | The SQL template the runtime executes. References to `#{xxx}` are filled from request params. |
| `sDataTest`, `sDataTestNode` | Sample request/response used by the BACK API tester (`POST /api/apiTest`). |
| `bHasToken` | `1` → require a token in `Authorization`; `0` → public. |

## API administration endpoints

`/api/list/...`, `/api/data/...`, `/api/add`, `/api/edit`, `/api/remove`
are the admin surface for managing `sysapi` rows themselves (the BACK
"自定义接口" screen calls these). `POST /api/getSqlTemple` produces a
SQL skeleton for a given API name; `POST /api/apiTest` runs an end-to-end
dry-run against the test fixture in `sDataTest`.

## Token endpoints — `/token/*`

| Endpoint | Method | Purpose |
|---|---|---|
| `/token/getToken?corpid=&corpsecret=` | GET / POST | Issue a bearer token for an integrator's `(corpid, corpsecret)` pair. (Mapping is method-agnostic.) |

The token returned is what `/api/invoke/{sApiCode}` expects in
`Authorization`. The full implementation is in
`xlyApi/src/main/java/com/xly/api/web/TokenController.java` and its
service.

For *third-party* tokens that xly itself fetches (so it can call
out-of-cluster APIs on behalf of a customer), see `/thirdtoken/*`,
backed by `sysapithirdtoken` (table comment: 第三方token获取接口).

## Other curated surfaces

These are smaller specialised APIs hosted in the same WAR:

| Endpoint root | Controller | Purpose |
|---|---|---|
| `/online/api/{sApiCode}` | `OnlineController` | Renders the BACK in-browser API debug/console page for the given `sysapi` row (returns a Thymeleaf view, not API execution). |
| `/online/onlineword/{sApiCode}` | `OnlineController` | Renders the "word"-style API documentation page. |
| `/online/onlinelist` | `OnlineController` | Renders the online-API listing page. |
| `/online/getToken` | `OnlineController` | Renders the in-browser token-acquisition helper page. |
| `/pro/get/{sProName}` | `ProContentController` | Renders the BACK page that displays a stored procedure's source. |
| `/pro/getData/{sProName}` | `ProContentController` | Returns the stored procedure's source text (not its result-set). |
| `/pro/alert/{redisId}` | `ProContentController` | Renders the alert/notification display page for the given Redis key. |
| `/pro/getAlertValue/{redisId}` | `ProContentController` | Returns the value cached in Redis under `redisId`. |
| `/pro/executeSql` | `ProContentController` | Executes a `sSql` payload directly (admin-tier dev tool). |
| `/thirdparty/*` | `ThirdPartyController` | CRUD over third-party-API definitions and the `checkPartyApi` validator. Backed by `sysapithirdparty`. |
| `/thirdtoken/*` | `ThirdTokenController` | CRUD over outbound-token configs. Backed by `sysapithirdtoken`. |
| `/brand/*` | `BrandController` | CRUD over the partner-supplier list (`sysapibrand`). |
| `/json/*`, `/json/jsonEdit/{sDomId}` | `JsonController` | JSON-tree edit helpers used by the BACK API editor. |
| `/interfaceDefine/callthirdparty/{interfaceInvoke}` | `InterfaceController` | Dispatch an outbound call defined in `sysapithirdparty`. |
| `/gpt/chatGpt` | `GptController` | Pass-through for the experimental GPT/chat surface — out-of-scope of the framework wiki. |

## Backing tables (DB-level surface)

These tables hold the API metadata. All carry `sBrandsId` /
`sSubsidiaryId` for tenant scoping.

| Table | Role |
|---|---|
| `sysapi` | API definitions used by `/api/invoke`. |
| `sysapilog` | Per-call audit log. |
| `sysapibrand` | Partner / supplier directory. |
| `sysapithirdparty` | Outbound third-party endpoint definitions. |
| `sysapithirdtoken` | Outbound third-party token configs. |
| `sysapidbtodb` | DB-to-DB sync API definitions. **Owned by `xlyFlow`'s `DbToDbController`, not xlyApi** — listed here because the table lives in xlyApi's `sysapi.sql`. |
| `sysapidbtodblog` | DB-to-DB sync run log. Same — written by xlyFlow. |

## How an integrator uses this

A typical first integration:

1. **Have an admin register your API.** Insert a row into `sysapi` (via
   the BACK self-service screen or directly): pick `sApiCode`, write
   `sDataSql`, set `bHasToken = 1` if you want token auth, save.
2. **Get a token.** `POST /xlyApi/token/getToken?corpid=<your_id>&corpsecret=<your_secret>`.
3. **Call the API.** `POST /xlyApi/api/invoke/<your_sApiCode>` with the
   token in `Authorization` and your payload as the JSON body.
4. **Inspect the log.** `sysapilog` records every call, the response
   `iStatus`, and the body. The BACK admin screen surfaces the same.

## What this API is *not*

- **Not OpenAPI-described** — the contract for any given `sApiCode` is
  whatever its `sysapi.sDataSql` and `sBody` rows say. Get the spec
  from the team that registered the API, not from a `/swagger`
  endpoint here.
- **Not a thin wrapper over `xlyEntry`'s internal API** — these are
  intentionally separate surfaces. Calling `/business/*` on `xlyEntry`
  from outside is not supported.
- **Not the inbound webhook receiver** — push events from third
  parties go through [`xlyInterface`](webhooks.md).
