2026-03-04 10:41:38 +08:00
|
|
|
|
# 🔌 API集成指南
|
|
|
|
|
|
|
|
|
|
|
|
> 版本: 1.0
|
|
|
|
|
|
> 更新时间: 2026-03-04
|
|
|
|
|
|
|
|
|
|
|
|
## 📋 目录
|
|
|
|
|
|
|
|
|
|
|
|
1. [快速开始](#快速开始)
|
|
|
|
|
|
2. [认证配置](#认证配置)
|
|
|
|
|
|
3. [SDK集成](#sdk集成)
|
|
|
|
|
|
4. [常见场景](#常见场景)
|
|
|
|
|
|
5. [错误处理](#错误处理)
|
|
|
|
|
|
6. [最佳实践](#最佳实践)
|
|
|
|
|
|
|
|
|
|
|
|
## 🚀 快速开始
|
|
|
|
|
|
|
|
|
|
|
|
### 前置条件
|
|
|
|
|
|
|
|
|
|
|
|
- 已获取API密钥(通过管理后台创建)
|
|
|
|
|
|
- 已获取Bearer Token(用于用户相关接口)
|
|
|
|
|
|
- 了解基本的RESTful API概念
|
|
|
|
|
|
|
|
|
|
|
|
### 5分钟快速集成
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 1. 配置API客户端
|
|
|
|
|
|
const API_BASE_URL = 'https://api.example.com';
|
|
|
|
|
|
const API_KEY = 'your-api-key-here';
|
|
|
|
|
|
const BEARER_TOKEN = 'your-bearer-token-here';
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 创建活动
|
|
|
|
|
|
async function createActivity() {
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/v1/activities`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'X-API-Key': API_KEY,
|
|
|
|
|
|
'Authorization': `Bearer ${BEARER_TOKEN}`
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
name: '春季特惠活动',
|
|
|
|
|
|
startTime: '2025-03-01T10:00:00+08:00',
|
|
|
|
|
|
endTime: '2025-03-31T23:59:59+08:00'
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
console.log('活动创建成功:', result.data);
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 获取排行榜
|
|
|
|
|
|
async function getLeaderboard(activityId) {
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/activities/${activityId}/leaderboard?page=0&size=20`,
|
|
|
|
|
|
{
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'X-API-Key': API_KEY,
|
|
|
|
|
|
'Authorization': `Bearer ${BEARER_TOKEN}`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
console.log('排行榜数据:', result.data);
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 创建分享跟踪
|
|
|
|
|
|
async function trackShare(activityId, userId) {
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/v1/share/track`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'X-API-Key': API_KEY,
|
|
|
|
|
|
'Authorization': `Bearer ${BEARER_TOKEN}`
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
activityId: activityId,
|
|
|
|
|
|
inviterUserId: userId,
|
|
|
|
|
|
source: 'wechat'
|
|
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
console.log('分享跟踪创建成功:', result.data);
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 🔐 认证配置
|
|
|
|
|
|
|
2026-03-23 13:02:36 +08:00
|
|
|
|
### 认证矩阵
|
|
|
|
|
|
|
|
|
|
|
|
本系统使用两种认证方式:API Key 和 Bearer Token(JWT)
|
|
|
|
|
|
|
|
|
|
|
|
| 路径模式 | 认证方式 | 说明 |
|
|
|
|
|
|
|----------|----------|------|
|
|
|
|
|
|
| `/r/**` | 无需认证 | 短链接跳转 |
|
|
|
|
|
|
| `/actuator/**` | 无需认证 | Spring Boot Actuator |
|
|
|
|
|
|
| `/api/v1/callback/**` | X-API-Key | 第三方回调接口 |
|
|
|
|
|
|
| `/api/v1/share/**` | X-API-Key + Bearer Token | 分享跟踪接口 |
|
|
|
|
|
|
| `/api/v1/me/**` | Bearer Token | 用户中心接口 |
|
|
|
|
|
|
| `/api/v1/activities/**` | Bearer Token | 用户端活动接口 |
|
|
|
|
|
|
| `/api/v1/activities/admin/**` | Bearer Token + 权限校验 | 管理后台活动接口 |
|
|
|
|
|
|
| `/api/v1/rewards/admin/**` | Bearer Token + 权限校验 | 管理后台奖励接口 |
|
|
|
|
|
|
| `/api/v1/roles/**` | Bearer Token + 权限校验 | 角色管理接口 |
|
|
|
|
|
|
| `/api/v1/departments/**` | Bearer Token + 权限校验 | 部门管理接口 |
|
|
|
|
|
|
| `/api/v1/approval/**` | Bearer Token + 权限校验 | 审批中心接口 |
|
|
|
|
|
|
| `/api/v1/users/**` | Bearer Token + 权限校验 | 用户管理接口 |
|
|
|
|
|
|
| `/api/v1/permissions/**` | Bearer Token + 权限校验 | 权限管理接口 |
|
|
|
|
|
|
| `/api/v1/invites/**` | Bearer Token + 权限校验 | 邀请管理接口 |
|
|
|
|
|
|
| `/api/v1/notifications/**` | Bearer Token + 权限校验 | 通知管理接口 |
|
|
|
|
|
|
| `/api/v1/risk/**` | Bearer Token + 权限校验 | 风险管理接口 |
|
|
|
|
|
|
| `/api/v1/audit/**` | Bearer Token + 权限校验 | 审计日志接口 |
|
|
|
|
|
|
| `/api/v1/system/**` | Bearer Token + 权限校验 | 系统管理接口 |
|
|
|
|
|
|
| `/api/v1/dashboard/**` | Bearer Token + 权限校验 | 仪表盘接口 |
|
|
|
|
|
|
| `/api/v1/api-keys/**` | Bearer Token + 权限校验 | API密钥管理接口 |
|
|
|
|
|
|
|
2026-03-04 10:41:38 +08:00
|
|
|
|
### API密钥认证
|
|
|
|
|
|
|
2026-03-23 13:02:36 +08:00
|
|
|
|
仅第三方回调和分享跟踪接口需要 `X-API-Key` 请求头:
|
2026-03-04 10:41:38 +08:00
|
|
|
|
|
|
|
|
|
|
```http
|
2026-03-23 13:02:36 +08:00
|
|
|
|
POST /api/v1/callback/register
|
2026-03-04 10:41:38 +08:00
|
|
|
|
X-API-Key: a1b2c3d4-e5f6-7890-1234-567890abcdef
|
2026-03-23 13:02:36 +08:00
|
|
|
|
Content-Type: application/json
|
|
|
|
|
|
|
|
|
|
|
|
{"trackingId": "track-abc123", "externalUserId": "user456", "timestamp": 1699999999999}
|
2026-03-04 10:41:38 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**获取API密钥:**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
curl -X POST https://api.example.com/api/v1/api-keys \
|
|
|
|
|
|
-H "Content-Type: application/json" \
|
|
|
|
|
|
-H "X-API-Key: admin-key" \
|
|
|
|
|
|
-H "Authorization: Bearer admin-token" \
|
|
|
|
|
|
-d '{
|
|
|
|
|
|
"activityId": 1,
|
|
|
|
|
|
"name": "我的应用密钥"
|
|
|
|
|
|
}'
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Bearer Token认证
|
|
|
|
|
|
|
|
|
|
|
|
用户相关端点需要 `Authorization` 请求头:
|
|
|
|
|
|
|
|
|
|
|
|
```http
|
|
|
|
|
|
GET /api/v1/me/invitation-info
|
|
|
|
|
|
X-API-Key: a1b2c3d4-e5f6-7890-1234-567890abcdef
|
|
|
|
|
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 双重认证
|
|
|
|
|
|
|
|
|
|
|
|
某些端点需要同时提供API密钥和Bearer Token:
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const headers = {
|
|
|
|
|
|
'X-API-Key': API_KEY,
|
|
|
|
|
|
'Authorization': `Bearer ${BEARER_TOKEN}`,
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 📦 SDK集成
|
|
|
|
|
|
|
|
|
|
|
|
### Java SDK
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- Maven依赖 -->
|
|
|
|
|
|
<dependency>
|
|
|
|
|
|
<groupId>com.mosquito</groupId>
|
|
|
|
|
|
<artifactId>mosquito-sdk</artifactId>
|
|
|
|
|
|
<version>1.0.0</version>
|
|
|
|
|
|
</dependency>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 初始化客户端
|
|
|
|
|
|
MosquitoClient client = MosquitoClient.builder()
|
|
|
|
|
|
.baseUrl("https://api.example.com")
|
|
|
|
|
|
.apiKey("your-api-key")
|
|
|
|
|
|
.bearerToken("your-bearer-token")
|
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建活动
|
|
|
|
|
|
Activity activity = client.activities()
|
|
|
|
|
|
.create(CreateActivityRequest.builder()
|
|
|
|
|
|
.name("春季特惠活动")
|
|
|
|
|
|
.startTime(ZonedDateTime.now())
|
|
|
|
|
|
.endTime(ZonedDateTime.now().plusDays(30))
|
|
|
|
|
|
.build());
|
|
|
|
|
|
|
|
|
|
|
|
// 获取排行榜
|
|
|
|
|
|
List<LeaderboardEntry> leaderboard = client.activities()
|
|
|
|
|
|
.getLeaderboard(activityId, 0, 20);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建分享跟踪
|
|
|
|
|
|
ShareTrackingResponse tracking = client.share()
|
|
|
|
|
|
.track(activityId, userId, "wechat");
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### JavaScript/TypeScript SDK
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
npm install @mosquito/sdk
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
import { MosquitoClient } from '@mosquito/sdk';
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化客户端
|
|
|
|
|
|
const client = new MosquitoClient({
|
|
|
|
|
|
baseUrl: 'https://api.example.com',
|
|
|
|
|
|
apiKey: 'your-api-key',
|
|
|
|
|
|
bearerToken: 'your-bearer-token'
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 创建活动
|
|
|
|
|
|
const activity = await client.activities.create({
|
|
|
|
|
|
name: '春季特惠活动',
|
|
|
|
|
|
startTime: new Date('2025-03-01T10:00:00+08:00'),
|
|
|
|
|
|
endTime: new Date('2025-03-31T23:59:59+08:00')
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 获取排行榜
|
|
|
|
|
|
const leaderboard = await client.activities.getLeaderboard(activityId, {
|
|
|
|
|
|
page: 0,
|
|
|
|
|
|
size: 20
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 创建分享跟踪
|
|
|
|
|
|
const tracking = await client.share.track({
|
|
|
|
|
|
activityId,
|
|
|
|
|
|
inviterUserId: userId,
|
|
|
|
|
|
source: 'wechat'
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Python SDK
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
pip install mosquito-sdk
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
from mosquito import MosquitoClient
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化客户端
|
|
|
|
|
|
client = MosquitoClient(
|
|
|
|
|
|
base_url='https://api.example.com',
|
|
|
|
|
|
api_key='your-api-key',
|
|
|
|
|
|
bearer_token='your-bearer-token'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建活动
|
|
|
|
|
|
activity = client.activities.create(
|
|
|
|
|
|
name='春季特惠活动',
|
|
|
|
|
|
start_time='2025-03-01T10:00:00+08:00',
|
|
|
|
|
|
end_time='2025-03-31T23:59:59+08:00'
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 获取排行榜
|
|
|
|
|
|
leaderboard = client.activities.get_leaderboard(
|
|
|
|
|
|
activity_id=activity_id,
|
|
|
|
|
|
page=0,
|
|
|
|
|
|
size=20
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建分享跟踪
|
|
|
|
|
|
tracking = client.share.track(
|
|
|
|
|
|
activity_id=activity_id,
|
|
|
|
|
|
inviter_user_id=user_id,
|
|
|
|
|
|
source='wechat'
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 🎯 常见场景
|
|
|
|
|
|
|
|
|
|
|
|
### 场景1:用户邀请流程
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 1. 用户登录后获取邀请信息
|
|
|
|
|
|
async function getUserInvitationInfo(activityId, userId) {
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/me/invitation-info?activityId=${activityId}&userId=${userId}`,
|
|
|
|
|
|
{ headers: { 'X-API-Key': API_KEY, 'Authorization': `Bearer ${BEARER_TOKEN}` } }
|
|
|
|
|
|
);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data; // { code, path, originalUrl }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 生成分享海报
|
|
|
|
|
|
async function generatePoster(activityId, userId, template = 'default') {
|
|
|
|
|
|
const imageUrl = `${API_BASE_URL}/api/v1/me/poster/image?activityId=${activityId}&userId=${userId}&template=${template}`;
|
|
|
|
|
|
return imageUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 用户分享后创建跟踪
|
|
|
|
|
|
async function trackUserShare(activityId, userId, source) {
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/v1/share/track`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'X-API-Key': API_KEY,
|
|
|
|
|
|
'Authorization': `Bearer ${BEARER_TOKEN}`
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({ activityId, inviterUserId: userId, source })
|
|
|
|
|
|
});
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 查看邀请的好友列表
|
|
|
|
|
|
async function getInvitedFriends(activityId, userId, page = 0) {
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/me/invited-friends?activityId=${activityId}&userId=${userId}&page=${page}&size=20`,
|
|
|
|
|
|
{ headers: { 'X-API-Key': API_KEY, 'Authorization': `Bearer ${BEARER_TOKEN}` } }
|
|
|
|
|
|
);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 查看用户奖励
|
|
|
|
|
|
async function getUserRewards(activityId, userId, page = 0) {
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/me/rewards?activityId=${activityId}&userId=${userId}&page=${page}&size=20`,
|
|
|
|
|
|
{ headers: { 'X-API-Key': API_KEY, 'Authorization': `Bearer ${BEARER_TOKEN}` } }
|
|
|
|
|
|
);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 场景2:活动数据分析
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 1. 获取活动统计数据
|
|
|
|
|
|
async function getActivityStats(activityId) {
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/activities/${activityId}/stats`,
|
|
|
|
|
|
{ headers: { 'X-API-Key': API_KEY, 'Authorization': `Bearer ${BEARER_TOKEN}` } }
|
|
|
|
|
|
);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data; // { totalParticipants, totalShares, dailyStats }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取裂变网络图
|
|
|
|
|
|
async function getActivityGraph(activityId, rootUserId = null, maxDepth = 3) {
|
|
|
|
|
|
const params = new URLSearchParams({
|
|
|
|
|
|
...(rootUserId && { rootUserId }),
|
|
|
|
|
|
maxDepth,
|
|
|
|
|
|
limit: 1000
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/activities/${activityId}/graph?${params}`,
|
|
|
|
|
|
{ headers: { 'X-API-Key': API_KEY, 'Authorization': `Bearer ${BEARER_TOKEN}` } }
|
|
|
|
|
|
);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data; // { nodes, edges }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 获取分享指标
|
|
|
|
|
|
async function getShareMetrics(activityId, startTime, endTime) {
|
|
|
|
|
|
const params = new URLSearchParams({
|
|
|
|
|
|
activityId,
|
|
|
|
|
|
...(startTime && { startTime }),
|
|
|
|
|
|
...(endTime && { endTime })
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/share/metrics?${params}`,
|
|
|
|
|
|
{ headers: { 'X-API-Key': API_KEY, 'Authorization': `Bearer ${BEARER_TOKEN}` } }
|
|
|
|
|
|
);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 获取转化漏斗
|
|
|
|
|
|
async function getConversionFunnel(activityId, startTime, endTime) {
|
|
|
|
|
|
const params = new URLSearchParams({
|
|
|
|
|
|
activityId,
|
|
|
|
|
|
...(startTime && { startTime }),
|
|
|
|
|
|
...(endTime && { endTime })
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/share/funnel?${params}`,
|
|
|
|
|
|
{ headers: { 'X-API-Key': API_KEY, 'Authorization': `Bearer ${BEARER_TOKEN}` } }
|
|
|
|
|
|
);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 导出排行榜CSV
|
|
|
|
|
|
async function exportLeaderboard(activityId, topN = null) {
|
|
|
|
|
|
const params = new URLSearchParams({
|
|
|
|
|
|
...(topN && { topN })
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(
|
|
|
|
|
|
`${API_BASE_URL}/api/v1/activities/${activityId}/leaderboard/export?${params}`,
|
|
|
|
|
|
{ headers: { 'X-API-Key': API_KEY, 'Authorization': `Bearer ${BEARER_TOKEN}` } }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const blob = await response.blob();
|
|
|
|
|
|
const url = window.URL.createObjectURL(blob);
|
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
|
a.href = url;
|
|
|
|
|
|
a.download = `leaderboard_${activityId}.csv`;
|
|
|
|
|
|
a.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 场景3:Webhook回调集成
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// 1. 注册Webhook
|
|
|
|
|
|
async function registerWebhook(activityId, callbackUrl, events) {
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}/api/v1/callback/register`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'X-API-Key': API_KEY,
|
|
|
|
|
|
'Authorization': `Bearer ${BEARER_TOKEN}`
|
|
|
|
|
|
},
|
|
|
|
|
|
body: JSON.stringify({
|
2026-03-23 13:02:36 +08:00
|
|
|
|
trackingId: 'track-abc123',
|
|
|
|
|
|
externalUserId: 'user456',
|
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
|
deviceFingerprint: 'fp-xxx',
|
|
|
|
|
|
ip: '192.168.1.1'
|
2026-03-04 10:41:38 +08:00
|
|
|
|
})
|
|
|
|
|
|
});
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 处理Webhook回调(服务端)
|
|
|
|
|
|
const express = require('express');
|
|
|
|
|
|
const crypto = require('crypto');
|
|
|
|
|
|
|
|
|
|
|
|
app.post('/webhook', express.json(), (req, res) => {
|
|
|
|
|
|
const signature = req.headers['x-webhook-signature'];
|
|
|
|
|
|
const payload = JSON.stringify(req.body);
|
|
|
|
|
|
const secret = 'your-webhook-secret';
|
|
|
|
|
|
|
|
|
|
|
|
// 验证签名
|
|
|
|
|
|
const expectedSignature = crypto
|
|
|
|
|
|
.createHmac('sha256', secret)
|
|
|
|
|
|
.update(payload)
|
|
|
|
|
|
.digest('hex');
|
|
|
|
|
|
|
|
|
|
|
|
if (signature !== `sha256=${expectedSignature}`) {
|
|
|
|
|
|
return res.status(401).send('Invalid signature');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理事件
|
|
|
|
|
|
const { eventType, data } = req.body;
|
|
|
|
|
|
|
|
|
|
|
|
switch (eventType) {
|
|
|
|
|
|
case 'user.registered':
|
|
|
|
|
|
console.log('新用户注册:', data);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'user.invited':
|
|
|
|
|
|
console.log('用户邀请:', data);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 'reward.granted':
|
|
|
|
|
|
console.log('奖励发放:', data);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
res.status(200).send('OK');
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## ⚠️ 错误处理
|
|
|
|
|
|
|
|
|
|
|
|
### 统一错误处理
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
class APIError extends Error {
|
|
|
|
|
|
constructor(code, message, details) {
|
|
|
|
|
|
super(message);
|
|
|
|
|
|
this.code = code;
|
|
|
|
|
|
this.details = details;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function apiCall(url, options) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(url, options);
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (result.code !== 200 && result.code !== 201) {
|
|
|
|
|
|
throw new APIError(
|
|
|
|
|
|
result.error?.code || 'UNKNOWN_ERROR',
|
|
|
|
|
|
result.message || 'Unknown error',
|
|
|
|
|
|
result.error?.details
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error instanceof APIError) {
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
throw new APIError('NETWORK_ERROR', 'Network request failed', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用示例
|
|
|
|
|
|
try {
|
|
|
|
|
|
const activity = await apiCall(`${API_BASE_URL}/api/v1/activities/1`, {
|
|
|
|
|
|
headers: { 'X-API-Key': API_KEY }
|
|
|
|
|
|
});
|
|
|
|
|
|
console.log('活动数据:', activity);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error.code === 'NOT_FOUND') {
|
|
|
|
|
|
console.error('活动不存在');
|
|
|
|
|
|
} else if (error.code === 'INVALID_API_KEY') {
|
|
|
|
|
|
console.error('API密钥无效');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('请求失败:', error.message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 重试机制
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
async function apiCallWithRetry(url, options, maxRetries = 3) {
|
|
|
|
|
|
let lastError;
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await fetch(url, options);
|
|
|
|
|
|
|
|
|
|
|
|
// 处理速率限制
|
|
|
|
|
|
if (response.status === 429) {
|
|
|
|
|
|
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
|
|
|
|
|
|
console.log(`速率限制,等待 ${retryAfter} 秒后重试...`);
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理服务器错误
|
|
|
|
|
|
if (response.status >= 500) {
|
|
|
|
|
|
console.log(`服务器错误,${i + 1}/${maxRetries} 次重试...`);
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
lastError = error;
|
|
|
|
|
|
if (i < maxRetries - 1) {
|
|
|
|
|
|
console.log(`网络错误,${i + 1}/${maxRetries} 次重试...`);
|
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw lastError;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 💡 最佳实践
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 使用连接池
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// Node.js
|
|
|
|
|
|
const https = require('https');
|
|
|
|
|
|
|
|
|
|
|
|
const agent = new https.Agent({
|
|
|
|
|
|
keepAlive: true,
|
|
|
|
|
|
maxSockets: 50,
|
|
|
|
|
|
maxFreeSockets: 10,
|
|
|
|
|
|
timeout: 60000
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const response = await fetch(url, {
|
|
|
|
|
|
agent,
|
|
|
|
|
|
headers: { 'X-API-Key': API_KEY }
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 实现请求缓存
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
const cache = new Map();
|
|
|
|
|
|
const CACHE_TTL = 60000; // 1分钟
|
|
|
|
|
|
|
|
|
|
|
|
async function cachedApiCall(url, options, ttl = CACHE_TTL) {
|
|
|
|
|
|
const cacheKey = `${url}:${JSON.stringify(options)}`;
|
|
|
|
|
|
const cached = cache.get(cacheKey);
|
|
|
|
|
|
|
|
|
|
|
|
if (cached && Date.now() - cached.timestamp < ttl) {
|
|
|
|
|
|
return cached.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const data = await apiCall(url, options);
|
|
|
|
|
|
cache.set(cacheKey, { data, timestamp: Date.now() });
|
|
|
|
|
|
|
|
|
|
|
|
return data;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 批量请求优化
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
async function batchGetActivities(activityIds) {
|
|
|
|
|
|
// 并发请求,但限制并发数
|
|
|
|
|
|
const BATCH_SIZE = 10;
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < activityIds.length; i += BATCH_SIZE) {
|
|
|
|
|
|
const batch = activityIds.slice(i, i + BATCH_SIZE);
|
|
|
|
|
|
const promises = batch.map(id =>
|
|
|
|
|
|
apiCall(`${API_BASE_URL}/api/v1/activities/${id}`, {
|
|
|
|
|
|
headers: { 'X-API-Key': API_KEY }
|
|
|
|
|
|
})
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const batchResults = await Promise.all(promises);
|
|
|
|
|
|
results.push(...batchResults);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 监控和日志
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
function logApiCall(url, options, duration, result) {
|
|
|
|
|
|
console.log({
|
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: options.method || 'GET',
|
|
|
|
|
|
duration: `${duration}ms`,
|
|
|
|
|
|
status: result.code,
|
|
|
|
|
|
success: result.code === 200 || result.code === 201
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function monitoredApiCall(url, options) {
|
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await apiCall(url, options);
|
|
|
|
|
|
logApiCall(url, options, Date.now() - startTime, result);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
logApiCall(url, options, Date.now() - startTime, { code: error.code, error: error.message });
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5. 安全最佳实践
|
|
|
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
// ❌ 不要在客户端暴露API密钥
|
|
|
|
|
|
// const API_KEY = 'a1b2c3d4-e5f6-7890-1234-567890abcdef';
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 通过后端代理API请求
|
|
|
|
|
|
async function proxyApiCall(endpoint, options) {
|
|
|
|
|
|
const response = await fetch(`/api/proxy${endpoint}`, {
|
|
|
|
|
|
...options,
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
...options.headers,
|
|
|
|
|
|
'Authorization': `Bearer ${userToken}` // 只传用户token
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return await response.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 后端代理(Node.js/Express)
|
|
|
|
|
|
app.use('/api/proxy', async (req, res) => {
|
|
|
|
|
|
const response = await fetch(`${API_BASE_URL}${req.path}`, {
|
|
|
|
|
|
method: req.method,
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'X-API-Key': process.env.API_KEY, // 从环境变量读取
|
|
|
|
|
|
'Authorization': req.headers.authorization,
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
},
|
|
|
|
|
|
body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
res.json(result);
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 📚 相关资源
|
|
|
|
|
|
|
|
|
|
|
|
- [API文档](./api.md) - 完整的API端点参考
|
|
|
|
|
|
- [部署指南](./DEPLOYMENT_GUIDE.md) - 如何部署应用
|
|
|
|
|
|
- [配置指南](./CONFIGURATION_GUIDE.md) - 配置选项说明
|
|
|
|
|
|
- [开发指南](./DEVELOPMENT_GUIDE.md) - 如何参与开发
|
|
|
|
|
|
|
|
|
|
|
|
## 🆘 获取帮助
|
|
|
|
|
|
|
|
|
|
|
|
- **技术支持**: support@example.com
|
|
|
|
|
|
- **问题反馈**: https://github.com/your-org/mosquito/issues
|
|
|
|
|
|
- **API状态**: https://status.example.com
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
**文档版本**: 1.0
|
|
|
|
|
|
**最后更新**: 2026-03-04
|