# 开发 mm API Event 事件

## 规则（Rules）

- Event 是**请求拦截/分发层**，通过 `target` 匹配 URL（支持 `*` 通配符），在请求各阶段执行拦截逻辑
- Event 本身不处理具体业务，通常通过 `$.admin.api().run(ctx, db)` **委托给 API 管理器**
- Event 分两个层级：
  - **App 级**：`app/{模块名}/event_api/{client|web|manage}/{事件名}/` — `event.json`（单对象）+ `main.js`
  - **Plugin 级**：`app/{模块名}/plugin/{插件名}/event_api/{事件名}/` — `event.json`（数组）+ `index.js`/`before.js`/`after.js`
- 请求生命周期有 **5 个阶段**：`before` → `check` → `main` → `render` → `after`
- **所有开发操作前必须先调用 `get_run_path` 获取运行路径**

## 方法（Methods）

### 步骤0：获取运行路径

使用 `get_run_path` 工具获取当前程序运行根目录，确认 mm_os 项目路径。

### 步骤1：确定 Event 层级和阶段

**选择层级：**
- **App 级** — 拦截整个 App 的请求（如 `/api/*` 拦截所有 API）
- **Plugin 级** — 仅拦截特定插件的请求，适合插件内部预处理/后处理

**选择阶段（5选1或多选）：**

| stage | 时机 | 典型用途 |
|:------|:-----|:---------|
| `before` | 请求进入，业务前 | 权限校验、参数预处理、URL转换 |
| `check` | 参数校验阶段 | 安装检测、环境验证 |
| `main` | 核心业务阶段 | 请求分发（通过 `api.run` 委托） |
| `render` | 模板渲染前 | 数据准备、模型注入 |
| `after` | 响应返回前 | 后处理、数据转换、URL签名 |

### 步骤2：App 级 Event 开发

**目录结构：**
```
app/{模块名}/event_api/{client|web|manage}/{事件名}/
├── event.json
└── main.js
```

**event.json（单对象格式）：**
```json
{
    "target": "/api/*",
    "name": "sys_api",
    "title": "系统API事件",
    "description": "拦截API请求并分发",
    "stage": "main",
    "main": "./main.js",
    "sort": 110,
    "end": true
}
```

**main.js（module.exports 导出）：**
```javascript
var api = $.admin.api('分组名', '分组标题');

module.exports = {
    async _init() {
        await api.call('update', 'app/');
    },

    async main(ctx, db) {
        $.push(db, $.admin.sql('sys').db(), true);
        return await api.run(ctx, db);
    }
};
```

### 步骤3：Plugin 级 Event 开发

**目录结构：**
```
app/{模块名}/plugin/{插件名}/event_api/{事件名}/
├── event.json      # 数组格式，定义各阶段的处理器
├── index.js        # stage: main — 核心分发（exports.main 导出）
├── before.js       # stage: before — 前置处理（可选）
└── after.js        # stage: after — 后置处理（可选）
```

**event.json（数组格式，支持多阶段）：**
```json
[
    {
        "target": "/api/*",
        "name": "my_event_before",
        "title": "前置处理",
        "stage": "before",
        "main": "./before.js",
        "sort": 10
    },
    {
        "target": "/api/*",
        "name": "my_event_main",
        "title": "核心分发",
        "stage": "main",
        "main": "./index.js",
        "sort": 20,
        "end": true
    },
    {
        "target": "/api/*",
        "name": "my_event_after",
        "title": "后置处理",
        "stage": "after",
        "main": "./after.js",
        "sort": 30
    }
]
```

**index.js（exports.main 导出，与 API 脚本风格一致）：**
```javascript
var api = $.admin.api('分组名', '分组标题');

async function main(ctx, db) {
    $.push(db, $.admin.sql('sys').db(), true);
    return await api.run(ctx, db);
}
exports.main = main;
```

**before.js（前置处理示例）：**
```javascript
async function main(ctx, db) {
    // 权限校验、参数预处理等
    var { query } = ctx.request;
    if (!query.token) {
        return $.ret.error(401, '未授权');
    }
    return null; // 返回 null 继续后续流程
}
exports.main = main;
```

**after.js（后置处理示例）：**
```javascript
async function main(ctx, db) {
    // 后处理、数据转换等
    return null;
}
exports.main = main;
```

### 步骤4：关键字段说明

| 字段 | 说明 |
|:-----|:------|
| `target` | URL 匹配模式，`*` 通配符。如 `/api/*` 匹配所有 API，`/upload*` 匹配上传路径 |
| `stage` | 触发阶段：`before` / `check` / `main` / `render` / `after` |
| `main` | 处理器脚本路径 |
| `sort` | 执行顺序，值越小越先执行 |
| `end` | `true` 终止事件链，后续事件不再执行 |

## 技巧（Tips）

- **Event 与 API 关系**：Event = 路由/分发层，API = 业务层。Event 通过 `target + *` 批量拦截，再通过 `api.run()` 分发给具体 API 处理
- **多阶段组合**：Plugin 级常用 `before`（预处理）+ `main`（分发）+ `after`（后处理）三阶段组合
- **导出方式区别**：App 级用 `module.exports = { _init, main }`，Plugin 级用 `exports.main = main`（和 API 脚本一致）
- **参考实现**：App 级 → `app/sys/event_api/web/`，Plugin 级 → `app/sys/plugin/tencent/event_api/change_url/`
