# 扩展功能API文档

## 概述

扩展功能允许开发者通过Webhook和开放API将平台功能与自己的业务系统对接，实现自动化业务流程。

## 功能特性

- **Webhook回调**: 实时接收平台事件通知
- **开放API**: 通过API密钥访问平台功能
- **签名验证**: 确保请求安全可靠
- **权限控制**: 细粒度的API访问权限管理

---

## 一、Webhook配置

### 1.1 创建Webhook

**接口**: `POST /api/v1/dev/extension/webhooks`

**请求参数**:
```json
{
  "application_id": 1,
  "name": "商城系统",
  "url": "https://your-server.com/webhook",
  "secret_key": "your_secret_key",
  "events": ["user.registered", "user.recharged"],
  "retry_count": 3,
  "timeout": 10
}
```

**参数说明**:
- `application_id` (必填): 应用ID
- `name` (必填): Webhook名称
- `url` (必填): 回调URL
- `secret_key` (可选): 密钥，用于验证回调请求
- `events` (必填): 订阅的事件类型数组
- `retry_count` (可选): 重试次数，默认3次
- `timeout` (可选): 超时时间（秒），默认10秒

### 1.2 查询Webhook列表

**接口**: `GET /api/v1/dev/extension/webhooks?application_id=1`

**响应示例**:
```json
{
  "code": 200,
  "data": [
    {
      "id": 1,
      "application_id": 1,
      "name": "商城系统",
      "url": "https://your-server.com/webhook",
      "secret_key": "your_secret_key",
      "events": "[\"user.registered\",\"user.recharged\"]",
      "status": "active",
      "retry_count": 3,
      "timeout": 10,
      "created_at": "2024-01-01T00:00:00Z"
    }
  ]
}
```

### 1.3 更新Webhook

**接口**: `PUT /api/v1/dev/extension/webhooks/:id`

**请求参数**: 同创建Webhook

### 1.4 删除Webhook

**接口**: `DELETE /api/v1/dev/extension/webhooks/:id`

### 1.5 测试Webhook

**接口**: `POST /api/v1/dev/extension/webhooks/:id/test`

### 1.6 查看Webhook日志

**接口**: `GET /api/v1/dev/extension/webhooks/logs?webhook_id=1`

---

## 二、Webhook事件

### 2.1 支持的事件类型

| 事件代码 | 事件名称 | 触发时机 |
|---------|---------|---------|
| `user.registered` | 用户注册 | 新用户注册成功时 |
| `user.login` | 用户登录 | 用户登录成功时 |
| `user.recharged` | 用户充值 | 用户充值成功时 |
| `user.expired` | 用户到期 | 用户会员到期时 |
| `card.used` | 卡密使用 | 卡密被使用时 |
| `card.expired` | 卡密过期 | 卡密过期时 |
| `abnormal.detected` | 异常检测 | 检测到异常行为时 |

### 2.2 回调数据格式

当事件触发时，平台会向配置的URL发送POST请求：

```json
{
  "event": "user.registered",
  "timestamp": 1234567890,
  "data": {
    "user_id": 123,
    "username": "testuser",
    "email": "test@example.com",
    "application_id": 1
  }
}
```

### 2.3 验证回调请求

如果配置了`secret_key`，平台会在请求头中添加：

```
X-Webhook-Secret: your_secret_key
```

您的服务器应验证此头部以确保请求来自平台。

---

## 三、API密钥管理

### 3.1 创建API密钥

**接口**: `POST /api/v1/dev/extension/api-keys`

**请求参数**:
```json
{
  "application_id": 1,
  "name": "商城对接",
  "permissions": ["user.read", "user.recharge"],
  "expires_at": "2025-12-31T23:59:59Z"
}
```

**参数说明**:
- `application_id` (必填): 应用ID
- `name` (必填): 密钥名称
- `permissions` (可选): 权限列表
- `expires_at` (可选): 过期时间

**响应示例**:
```json
{
  "code": 200,
  "data": {
    "id": 1,
    "application_id": 1,
    "name": "商城对接",
    "access_key": "abc123...",
    "secret_key": "xyz789...",
    "permissions": "[\"user.read\",\"user.recharge\"]",
    "status": "active",
    "created_at": "2024-01-01T00:00:00Z"
  }
}
```

**重要**: `secret_key`只在创建时返回一次，请妥善保存！

### 3.2 查询API密钥列表

**接口**: `GET /api/v1/dev/extension/api-keys?application_id=1`

### 3.3 更新API密钥

**接口**: `PUT /api/v1/dev/extension/api-keys/:id`

### 3.4 删除API密钥

**接口**: `DELETE /api/v1/dev/extension/api-keys/:id`

### 3.5 重新生成密钥

**接口**: `POST /api/v1/dev/extension/api-keys/:id/regenerate`

**注意**: 重新生成后，旧密钥立即失效！

---

## 四、开放API接口

### 4.1 认证方式

所有开放API请求都需要进行签名认证：

**请求头**:
```
X-Access-Key: your_access_key
X-Signature: calculated_signature
X-Timestamp: 1234567890
```

**签名算法**:
```javascript
// 1. 构造签名字符串
const stringToSign = METHOD + PATH + TIMESTAMP + BODY

// 示例
const method = "POST"
const path = "/api/v1/ext/user/123/recharge"
const timestamp = "1234567890"
const body = JSON.stringify({
  "amount": 100,
  "type": "points",
  "description": "商城充值"
})

const stringToSign = method + path + timestamp + body
// 结果: "POST/api/v1/ext/user/123/recharge1234567890{\"amount\":100,\"type\":\"points\",\"description\":\"商城充值\"}"

// 2. 计算签名
const signature = HMAC-SHA256(secretKey, stringToSign)
```

**Python示例**:
```python
import hmac
import hashlib
import time
import json

def calculate_signature(secret_key, method, path, timestamp, body):
    string_to_sign = method + path + str(timestamp) + body
    signature = hmac.new(
        secret_key.encode('utf-8'),
        string_to_sign.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    return signature

# 使用示例
access_key = "your_access_key"
secret_key = "your_secret_key"
method = "POST"
path = "/api/v1/ext/user/123/recharge"
timestamp = int(time.time())
body = json.dumps({
    "amount": 100,
    "type": "points",
    "description": "商城充值"
})

signature = calculate_signature(secret_key, method, path, timestamp, body)

headers = {
    "X-Access-Key": access_key,
    "X-Signature": signature,
    "X-Timestamp": str(timestamp),
    "Content-Type": "application/json"
}
```

### 4.2 用户相关接口

#### 4.2.1 查询用户信息

**接口**: `GET /api/v1/ext/user/:userId`

**响应示例**:
```json
{
  "code": 200,
  "data": {
    "id": 123,
    "username": "testuser",
    "email": "test@example.com",
    "points": 1000,
    "level": 1,
    "status": "active",
    "expiry_at": "2024-12-31T23:59:59Z",
    "last_login_at": "2024-01-01T12:00:00Z",
    "created_at": "2024-01-01T00:00:00Z"
  }
}
```

#### 4.2.2 查询用户列表

**接口**: `GET /api/v1/ext/users?page=1&page_size=20&status=active&search=test`

**参数说明**:
- `page`: 页码，默认1
- `page_size`: 每页数量，默认20
- `status`: 用户状态筛选
- `search`: 搜索关键词（用户名或邮箱）

#### 4.2.3 为用户充值

**接口**: `POST /api/v1/ext/user/:userId/recharge`

**请求参数**:
```json
{
  "amount": 100,
  "type": "points",
  "description": "商城充值"
}
```

**参数说明**:
- `amount` (必填): 充值数量
- `type` (必填): 充值类型
  - `points`: 充值积分
  - `days`: 充值天数
- `description` (可选): 充值说明

#### 4.2.4 扣除用户余额

**接口**: `POST /api/v1/ext/user/:userId/deduct`

**请求参数**:
```json
{
  "amount": 50,
  "type": "points",
  "description": "购买商品"
}
```

#### 4.2.5 更新用户积分

**接口**: `POST /api/v1/ext/user/:userId/points`

**请求参数**:
```json
{
  "points": 500
}
```

### 4.3 卡密相关接口

#### 4.3.1 查询卡密列表

**接口**: `GET /api/v1/ext/cards?page=1&page_size=20&status=unused&card_type_id=1`

#### 4.3.2 生成卡密

**接口**: `POST /api/v1/ext/cards/generate`

**请求参数**:
```json
{
  "card_type_id": 1,
  "count": 10
}
```

#### 4.3.3 查询卡密详情

**接口**: `GET /api/v1/ext/card/:cardId`

### 4.4 通知相关接口

#### 4.4.1 发送通知

**接口**: `POST /api/v1/ext/notification`

**请求参数**:
```json
{
  "user_id": 123,
  "title": "系统通知",
  "content": "您的充值已到账",
  "type": "system"
}
```

#### 4.4.2 批量发送通知

**接口**: `POST /api/v1/ext/notification/batch`

**请求参数**:
```json
{
  "title": "系统公告",
  "content": "系统将于今晚维护",
  "type": "announcement"
}
```

### 4.5 应用信息接口

#### 4.5.1 获取应用信息

**接口**: `GET /api/v1/ext/app/info`

#### 4.5.2 获取应用统计

**接口**: `GET /api/v1/ext/app/stats`

**响应示例**:
```json
{
  "code": 200,
  "data": {
    "user_count": 1000,
    "active_user_count": 800,
    "card_count": 500,
    "used_card_count": 300
  }
}
```

---

## 五、权限说明

### 5.1 可用权限列表

| 权限代码 | 权限名称 | 说明 |
|---------|---------|------|
| `user.read` | 查看用户 | 查询用户信息和列表 |
| `user.recharge` | 用户充值 | 为用户充值积分或天数 |
| `user.deduct` | 用户扣费 | 扣除用户积分或天数 |
| `card.read` | 查看卡密 | 查询卡密信息和列表 |
| `card.generate` | 生成卡密 | 生成新的卡密 |
| `notification.send` | 发送通知 | 发送用户通知 |

---

## 六、错误码说明

| 错误码 | 说明 |
|-------|------|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 认证失败（缺少认证信息、签名错误、密钥无效等） |
| 403 | 权限不足 |
| 404 | 资源不存在 |
| 500 | 服务器内部错误 |

---

## 七、最佳实践

### 7.1 安全建议

1. **妥善保管密钥**: Secret Key只在创建时显示一次，请立即保存
2. **定期更换密钥**: 建议定期重新生成API密钥
3. **最小权限原则**: 只授予必要的权限
4. **验证Webhook**: 验证回调请求的来源和完整性
5. **使用HTTPS**: 确保所有通信都使用HTTPS

### 7.2 性能优化

1. **批量操作**: 使用批量接口减少请求次数
2. **缓存数据**: 合理缓存用户信息等数据
3. **异步处理**: Webhook回调建议异步处理业务逻辑

### 7.3 错误处理

1. **重试机制**: 实现请求重试逻辑，处理网络异常
2. **日志记录**: 记录所有API请求和响应，便于排查问题
3. **超时设置**: 合理设置请求超时时间

---

## 八、完整示例

### 8.1 Python示例

```python
import requests
import hmac
import hashlib
import time
import json

class ExtensionAPI:
    def __init__(self, access_key, secret_key, base_url="http://localhost:8080"):
        self.access_key = access_key
        self.secret_key = secret_key
        self.base_url = base_url
    
    def _calculate_signature(self, method, path, timestamp, body=""):
        string_to_sign = method + path + str(timestamp) + body
        signature = hmac.new(
            self.secret_key.encode('utf-8'),
            string_to_sign.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        return signature
    
    def _get_headers(self, method, path, body=""):
        timestamp = int(time.time())
        signature = self._calculate_signature(method, path, timestamp, body)
        return {
            "X-Access-Key": self.access_key,
            "X-Signature": signature,
            "X-Timestamp": str(timestamp),
            "Content-Type": "application/json"
        }
    
    def get_user(self, user_id):
        path = f"/api/v1/ext/user/{user_id}"
        headers = self._get_headers("GET", path)
        response = requests.get(f"{self.base_url}{path}", headers=headers)
        return response.json()
    
    def recharge_user(self, user_id, amount, recharge_type, description=""):
        path = f"/api/v1/ext/user/{user_id}/recharge"
        body = json.dumps({
            "amount": amount,
            "type": recharge_type,
            "description": description
        })
        headers = self._get_headers("POST", path, body)
        response = requests.post(f"{self.base_url}{path}", headers=headers, data=body)
        return response.json()

# 使用示例
api = ExtensionAPI("your_access_key", "your_secret_key")

# 查询用户
user = api.get_user(123)
print(user)

# 为用户充值100积分
result = api.recharge_user(123, 100, "points", "商城充值")
print(result)
```

### 8.2 Node.js示例

```javascript
const crypto = require('crypto');
const axios = require('axios');

class ExtensionAPI {
  constructor(accessKey, secretKey, baseUrl = 'http://localhost:8080') {
    this.accessKey = accessKey;
    this.secretKey = secretKey;
    this.baseUrl = baseUrl;
  }

  calculateSignature(method, path, timestamp, body = '') {
    const stringToSign = method + path + timestamp + body;
    return crypto
      .createHmac('sha256', this.secretKey)
      .update(stringToSign)
      .digest('hex');
  }

  getHeaders(method, path, body = '') {
    const timestamp = Math.floor(Date.now() / 1000);
    const signature = this.calculateSignature(method, path, timestamp, body);
    return {
      'X-Access-Key': this.accessKey,
      'X-Signature': signature,
      'X-Timestamp': timestamp.toString(),
      'Content-Type': 'application/json'
    };
  }

  async getUser(userId) {
    const path = `/api/v1/ext/user/${userId}`;
    const headers = this.getHeaders('GET', path);
    const response = await axios.get(`${this.baseUrl}${path}`, { headers });
    return response.data;
  }

  async rechargeUser(userId, amount, type, description = '') {
    const path = `/api/v1/ext/user/${userId}/recharge`;
    const body = JSON.stringify({ amount, type, description });
    const headers = this.getHeaders('POST', path, body);
    const response = await axios.post(`${this.baseUrl}${path}`, body, { headers });
    return response.data;
  }
}

// 使用示例
const api = new ExtensionAPI('your_access_key', 'your_secret_key');

// 查询用户
api.getUser(123).then(user => console.log(user));

// 为用户充值100积分
api.rechargeUser(123, 100, 'points', '商城充值').then(result => console.log(result));
```

---

## 九、常见问题

### Q1: 签名验证失败怎么办？

**A**: 请检查以下几点：
1. 确保使用正确的Secret Key
2. 确保时间戳是当前Unix时间戳（秒级）
3. 确保签名字符串的拼接顺序正确：METHOD + PATH + TIMESTAMP + BODY
4. 确保Body是原始JSON字符串，不要格式化或添加空格

### Q2: Webhook回调失败怎么办？

**A**: 
1. 检查回调URL是否可访问
2. 查看Webhook日志了解失败原因
3. 确保服务器能正确处理POST请求
4. 检查是否设置了正确的超时时间

### Q3: API密钥泄露了怎么办？

**A**: 立即删除旧密钥并创建新密钥，或者使用"重新生成"功能。

### Q4: 如何测试API接口？

**A**: 可以使用Postman、curl等工具，参考本文档中的签名算法构造请求。

---

## 十、技术支持

如有问题，请通过以下方式获取帮助：
- 查看平台文档
- 提交工单
- 联系技术支持
