# Aop 轻量级切面类使用文档

## 概述
`Aop` 是一个轻量级的 JavaScript 切面编程（AOP）工具类，支持为对象方法注册并执行 `before`（前置）、`after`（后置）、`error`（异常）三种类型的切面函数，适用于日志记录、参数校验、异常处理等横切关注点场景。

## 特性
- 支持链式调用注册切面函数
- 内置切面类型校验，仅支持 `before`/`after`/`error` 三种常用类型
- 支持切面规则启用/禁用，灵活控制切面执行
- 保留原方法上下文，保证方法执行正确性
- 完善的错误处理，切面执行异常不阻断主流程

## 安装与引入
### 方式1：ESModule 引入
```javascript
import { Aop, aop } from './aop.js';
```

## 核心 API

### 1. 切面注册（set）
注册一个切面函数，返回当前实例支持链式调用。

#### 语法
```javascript
aop.set(name, fn)
```

#### 参数
| 参数 | 类型 | 说明 |
|------|------|------|
| name | string | 切面名称（非空字符串） |
| fn | Function | 切面执行函数，入参为切面上下文对象 |

#### 示例
```javascript
// 注册日志切面
aop.set('logger', async (params) => {
  console.log(`[${params.methodName}] 执行时间：${new Date().toISOString()}`);
  console.log(`参数：`, params.args);
});

// 注册参数校验切面（链式调用）
aop.set('paramCheck', async (params) => {
  const [id] = params.args;
  if (!id || typeof id !== 'number') {
    throw new Error(`[${params.methodName}] 参数id必须是数字`);
  }
});
```

### 2. 获取切面（get）
获取已注册的切面函数。

#### 语法
```javascript
aop.get(name)
```

#### 参数
| 参数 | 类型 | 说明 |
|------|------|------|
| name | string | 切面名称 |

#### 返回值
| 类型 | 说明 |
|------|------|
| Function \| null | 存在则返回切面函数，否则返回 null |

#### 示例
```javascript
const logger = aop.get('logger');
if (logger) {
  console.log('日志切面已注册');
}
```

### 3. 移除切面（remove）
移除指定名称的切面函数。

#### 语法
```javascript
aop.remove(name)
```

#### 参数
| 参数 | 类型 | 说明 |
|------|------|------|
| name | string | 切面名称 |

#### 示例
```javascript
// 移除日志切面
aop.remove('logger');
```

### 4. 清空所有切面（clear）
清空已注册的所有切面函数。

#### 语法
```javascript
aop.clear()
```

#### 示例
```javascript
// 清空所有切面
aop.clear();
```

### 5. 绑定切面到方法（wrap）
核心方法，为目标对象的指定方法绑定切面规则。

#### 语法
```javascript
aop.wrap(instance, config)
```

#### 参数
| 参数 | 类型 | 说明 |
|------|------|------|
| instance | object | 目标实例（非空对象） |
| config | object | 切面配置，格式：`{ 方法名: [{ type: 切面类型, enabled: 是否启用, 切面名称: 配置 }] }` |

#### 配置规则说明
| 字段 | 类型 | 说明 | 必填 | 默认值 |
|------|------|------|------|--------|
| type | string | 切面类型（before/after/error） | 是 | - |
| enabled | boolean | 是否启用该规则 | 否 | true |
| [切面名称] | any | 自定义配置，会透传给切面函数 | 否 | {} |

#### 返回值
| 类型 | 说明 |
|------|------|
| object | 绑定后的实例对象 |

## 完整使用示例

### 步骤1：定义目标类
```javascript
// 示例控制器类
class UserController {
  async getUser(id) {
    console.log(`获取用户信息，ID：${id}`);
    if (id === 0) {
      throw new Error('用户ID不能为0');
    }
    return { id, name: '张三', age: 20 };
  }
}
```

### 步骤2：注册切面函数
```javascript
import { aop } from './aop.js';

// 1. 注册参数校验切面
aop.set('paramCheck', async (params) => {
  const [id] = params.args;
  console.log(`[paramCheck] 校验参数：id = ${id}`);
  if (typeof id !== 'number') {
    throw new Error(`参数id必须是数字，当前值：${id}`);
  }
});

// 2. 注册日志切面
aop.set('logger', async (params) => {
  const { methodName, args, result, error } = params;
  if (params.type === 'before') {
    console.log(`[logger] 方法${methodName}开始执行，参数：`, args);
  } else if (params.type === 'after') {
    console.log(`[logger] 方法${methodName}执行完成，结果：`, result);
  } else if (params.type === 'error') {
    console.log(`[logger] 方法${methodName}执行异常：`, error.message);
  }
});

// 3. 注册异常处理切面
aop.set('errorHandler', async (params) => {
  console.log(`[errorHandler] 捕获异常：${params.error.message}，执行兜底逻辑`);
  // 可在这里添加异常上报、数据清理等逻辑
});
```

### 步骤3：绑定切面到方法并使用
```javascript
// 创建实例
const userController = new UserController();

// 绑定切面规则
aop.wrap(userController, {
  getUser: [
    // 前置切面：参数校验 + 日志
    { type: 'before', enabled: true, paramCheck: true, logger: true },
    // 后置切面：日志
    { type: 'after', enabled: true, logger: true },
    // 异常切面：日志 + 异常处理
    { type: 'error', enabled: true, logger: true, errorHandler: true }
  ]
});

// 测试正常执行
async function testNormal() {
  try {
    const result = await userController.getUser(1);
    console.log('最终结果：', result);
  } catch (e) {
    console.log('测试异常：', e.message);
  }
}

// 测试异常执行
async function testError() {
  try {
    const result = await userController.getUser(0);
    console.log('最终结果：', result);
  } catch (e) {
    console.log('测试异常：', e.message);
  }
}

// 执行测试
testNormal();
// testError();
```

### 执行结果（testNormal）
```
[paramCheck] 校验参数：id = 1
[logger] 方法getUser开始执行，参数： [1]
获取用户信息，ID：1
[logger] 方法getUser执行完成，结果： { id: 1, name: '张三', age: 20 }
最终结果： { id: 1, name: '张三', age: 20 }
```

### 执行结果（testError）
```
[paramCheck] 校验参数：id = 0
[logger] 方法getUser开始执行，参数： [0]
获取用户信息，ID：0
[logger] 方法getUser执行异常： 用户ID不能为0
[errorHandler] 捕获异常：用户ID不能为0，执行兜底逻辑
测试异常： 用户ID不能为0
```

## 切面函数上下文参数说明
切面函数的入参是一个上下文对象，包含以下字段：

| 字段 | 类型 | 说明 | 可用切面类型 |
|------|------|------|--------------|
| ctx | object | 原方法的执行上下文（实例对象） | all |
| methodName | string | 被包装的方法名称 | all |
| args | Array | 原方法的入参数组（浅拷贝） | all |
| originalMethod | Function | 原始未包装的方法 | all |
| result | any | 原方法执行结果 | after |
| error | Error | 原方法抛出的异常 | error |
| params | object | 切面规则中配置的自定义参数 | all |

## 注意事项
1. **异步支持**：切面函数和被包装的方法均支持异步（async/await），内部会自动处理异步执行顺序
2. **上下文保留**：原方法的 `this` 指向会被正确保留，无需额外处理
3. **异常处理**：`error` 类型切面执行完成后，异常会被重新抛出，不会阻断原有异常流程
4. **规则过滤**：`enabled: false` 的规则会被自动过滤，不执行对应的切面
5. **切面名称冲突**：不要使用 `type`/`enabled` 作为切面名称（内置关键字）

### 总结
1. `Aop` 类核心提供切面注册（`set`）和方法包装（`wrap`）能力，仅支持 `before`/`after`/`error` 三种切面类型。
2. 切面函数接收包含上下文、参数、结果/异常的完整入参，可灵活实现各类横切逻辑。
3. 通过配置规则的 `enabled` 字段可动态控制切面是否生效，异常切面执行后会重新抛出异常，保证原有错误流程不受影响。