# API 文档

本文档详细说明了活动管理和API密钥管理相关的API端点。

## 统一响应封装

除图片/HTML/CSV等非 JSON 响应外，所有接口返回 `ApiResponse`：

- 成功响应：

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {},
    "meta": {
      "pagination": {
        "page": 0,
        "size": 20,
        "total": 100,
        "totalPages": 5,
        "hasNext": true,
        "hasPrevious": false
      }
    },
    "timestamp": "2025-09-30T12:34:56",
    "traceId": "trace-id"
  }
  ```

- 错误响应：

  ```json
  {
    "code": 400,
    "message": "请求参数校验失败",
    "error": {
      "message": "activityId 不能为空",
      "details": { "activityId": "must not be null" },
      "code": "VALIDATION_ERROR"
    },
    "timestamp": "2025-09-30T12:34:56",
    "traceId": "trace-id"
  }
  ```

## 认证与鉴权

- `/api/**` 需要 `X-API-Key`。
- `/api/v1/me/**`、`/api/v1/activities/**`、`/api/v1/api-keys/**`、`/api/v1/share/**` 需要 `Authorization: Bearer <token>`。
- `/r/**`、`/actuator/**` 不需要认证。

## 错误码

- `VALIDATION_ERROR` → 400：请求参数校验失败（字段缺失/格式不符）。
- `BAD_REQUEST` → 400：业务数据不合法（如结束时间早于开始时间、上传文件不支持）。
- `FORBIDDEN` → 403：无权访问资源或操作。
- `NOT_FOUND` → 404：资源不存在（活动、API密钥等）。
- `INTERNAL_ERROR` → 500：服务器内部错误。
- `INVALID_API_KEY` → 401：提供的 API 密钥无效或已吊销。

## 1. 活动管理 (Activities)

### 1.1 创建活动

- **Endpoint**: `POST /api/v1/activities`
- **描述**: 创建一个新的营销活动。
- **请求体**: `application/json`

  ```json
  {
    "name": "春季特惠活动",
    "startTime": "2025-03-01T10:00:00+08:00",
    "endTime": "2025-03-31T23:59:59+08:00"
  }
  ```

- **成功响应 (201 Created)**:

  ```json
  {
    "code": 201,
    "message": "success",
    "data": {
      "id": 1,
      "name": "春季特惠活动",
      "startTime": "2025-03-01T10:00:00+08:00",
      "endTime": "2025-03-31T23:59:59+08:00"
    },
    "timestamp": "2025-03-01T10:00:00"
  }
  ```

- **失败响应**:
  - `400 Bad Request`: 如果请求数据无效（例如，名称为空或结束时间早于开始时间）。

### 1.2 更新活动

- **Endpoint**: `PUT /api/v1/activities/{id}`
- **描述**: 更新一个已存在的活动。
- **路径参数**: `id` (long) - 活动的唯一标识符。
- **请求体**: `application/json`

  ```json
  {
    "name": "春季特惠活动（升级版）",
    "startTime": "2025-03-01T10:00:00+08:00",
    "endTime": "2025-04-15T23:59:59+08:00"
  }
  ```

- **成功响应 (200 OK)**: `ApiResponse<Activity>`，`data` 为更新后的活动对象。
- **失败响应**:
  - `400 Bad Request`: 如果请求数据无效。
  - `404 Not Found`: 如果指定的 `id` 不存在。

### 1.3 获取活动详情

- **Endpoint**: `GET /api/v1/activities/{id}`
- **描述**: 获取指定ID的活动详情。
- **路径参数**: `id` (long) - 活动的唯一标识符。
- **成功响应 (200 OK)**: `ApiResponse<Activity>`，`data` 为活动对象。
- **失败响应**:
  - `404 Not Found`: 如果指定的 `id` 不存在。

## 2. API密钥管理 (API Keys)

### 2.1 创建API密钥

- **Endpoint**: `POST /api/v1/api-keys`
- **描述**: 为指定的活动创建一个新的API密钥。密钥仅在本次响应中明文返回，请立即保存。
- **请求体**: `application/json`

  ```json
  {
    "activityId": 1,
    "name": "我的第一个密钥"
  }
  ```

- **成功响应 (201 Created)**:

  ```json
  {
    "code": 201,
    "message": "success",
    "data": {
      "apiKey": "a1b2c3d4-e5f6-7890-1234-567890abcdef"
    },
    "timestamp": "2025-03-01T10:00:00"
  }
  ```

- **失败响应**:
  - `400 Bad Request`: 如果请求数据无效（例如，`activityId` 或 `name` 为空）。
  - `404 Not Found`: 如果 `activityId` 不存在。

### 2.2 吊销API密钥

- **Endpoint**: `DELETE /api/v1/api-keys/{id}`
- **描述**: 吊销（删除）一个API密钥。
- **路径参数**: `id` (long) - API密钥的唯一标识符。
- **成功响应 (200 OK)**: `ApiResponse<Void>`，`data` 为 `null`。
- **失败响应**:
  - `404 Not Found`: 如果指定的 `id` 不存在。

### 2.3 使用/校验 API 密钥

- Endpoint: `POST /api/v1/api-keys/{id}/use`
- 描述: 校验提供的明文 API 密钥是否与 `id` 对应的密钥匹配；校验成功将更新 `last_used_at`。
- 请求体: `application/json`

  ```json
  {
    "apiKey": "a1b2c3d4-e5f6-7890-1234-567890abcdef"
  }
  ```

- 成功响应 (200 OK): `ApiResponse<Void>`，`data` 为 `null`。
- 失败响应:
  - `401 Unauthorized` + `INVALID_API_KEY`：密钥错误或已吊销。
  - `404 Not Found`：`id` 不存在。

### 2.4 通过前缀校验 API 密钥（无需 ID）

- Endpoint: `POST /api/v1/api-keys/validate`
- 描述: 仅凭明文 API 密钥进行校验（服务端使用前缀快速定位候选密钥，再进行哈希校验）。校验成功将更新 `last_used_at`。
- 请求体: `application/json`

  ```json
  {
    "apiKey": "a1b2c3d4-e5f6-7890-1234-567890abcdef"
  }
  ```

- 成功响应 (200 OK): `ApiResponse<Void>`，`data` 为 `null`。
- 失败响应:
  - `401 Unauthorized` + `INVALID_API_KEY`：密钥错误或已吊销。

## 3. 缓存管理 (Cache)

### 3.1 清空某类缓存

- Endpoint: `DELETE /api/v1/cache/{cacheName}`
- 描述: 清空指定缓存空间（如 `leaderboards`、`activities`、`activity_stats`、`activity_graph`）。
- 成功响应 (204 No Content)

### 3.2 失效某个缓存键

- Endpoint: `DELETE /api/v1/cache/{cacheName}/{key}`
- 描述: 失效指定缓存空间下的某个键。
- 成功响应 (204 No Content)

## 4. 数据分析 (Analytics)

### 4.1 获取排行榜

- Endpoint: `GET /api/v1/activities/{id}/leaderboard`
- 描述: 返回指定活动的排行榜（已启用缓存 `leaderboards`）。支持分页与 TopN。
- 查询参数:
  - `topN` 可选：只取前 N 名（先截断再分页）。
  - `page` 可选，默认 `0`：页码（从 0 开始）。
  - `size` 可选，默认 `20`：每页条数。
- 成功响应 (200 OK):

  ```json
  {
    "code": 200,
    "message": "success",
    "data": [
      { "userId": 1, "userName": "用户A", "score": 1500 },
      { "userId": 2, "userName": "用户B", "score": 1200 }
    ],
    "meta": {
      "pagination": {
        "page": 0,
        "size": 20,
        "total": 2,
        "totalPages": 1,
        "hasNext": false,
        "hasPrevious": false
      }
    }
  }
  ```

### 4.2 导出排行榜CSV

- Endpoint: `GET /api/v1/activities/{id}/leaderboard/export`
- 描述: 导出排行榜为CSV文件并下载。支持 `topN` 仅导出前 N 名。
- 成功响应 (200 OK): 响应头 `Content-Type: text/csv;charset=UTF-8`，`Content-Disposition: attachment; filename="leaderboard_{id}.csv"`
- CSV示例:

  ```csv
  userId,userName,score
  1,用户A,1500
  2,用户B,1200
  ```

### 4.3 获取裂变网络图

- Endpoint: `GET /api/v1/activities/{id}/graph`
- 描述: 返回指定活动的裂变网络图。支持以某个用户为根、限定层级与结果规模。
- 查询参数:
  - `rootUserId` 可选：作为根节点的用户ID；为空则返回全量图（受 `limit` 限制）。
  - `maxDepth` 可选，默认 `3`：最大层级深度（从根出发，`1` 表示仅直推）。
  - `limit` 可选，默认 `1000`：返回的最大边数（超出将截断）。
- 成功响应 (200 OK):

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {
      "nodes": [ { "id": "1", "label": "用户1" } ],
      "edges": [ { "from": "1", "to": "2" } ]
    }
  }
  ```

### 4.4 获取仪表盘统计

- Endpoint: `GET /api/v1/activities/{id}/stats`
- 描述: 汇总 `daily_activity_stats` 表的数据（已启用缓存 `activity_stats`）。`participants` 对应每日新增注册数聚合。
- 成功响应 (200 OK):

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {
      "totalParticipants": 220,
      "totalShares": 110,
      "dailyStats": [
        { "date": "2025-09-28", "participants": 100, "shares": 50 },
        { "date": "2025-09-29", "participants": 120, "shares": 60 }
      ]
    }
  }
  ```

## 5. 短链接 (Short Links)

### 5.1 生成短链接（内部）

- Endpoint: `POST /api/v1/internal/shorten`
- 描述: 生成短链接记录，仅供内部服务或管理端调用。
- 请求体:

  ```json
  { "originalUrl": "https://example.com/landing?ref=abc" }
  ```

- 成功响应 (201 Created):

  ```json
  {
    "code": 201,
    "message": "success",
    "data": { "code": "abc12345", "path": "/r/abc12345", "originalUrl": "https://example.com/landing?ref=abc" }
  }
  ```

### 5.2 短链接重定向（公开）

- Endpoint: `GET /r/{code}`
- 描述: 302 跳转到短链对应的原始地址。
- 成功响应 (302 Found): 响应头 `Location: <originalUrl>`

## 6. 用户端体验 (User Experience)

### 6.1 获取用户专属邀请信息

- Endpoint: `GET /api/v1/me/invitation-info`
- 描述: 返回当前用户的专属短链接（示例以 query 参数的 `activityId`/`userId` 代替鉴权）。
- Query: `activityId`, `userId`
- 成功响应 (200 OK):

  ```json
  {
    "code": 200,
    "message": "success",
    "data": { "code": "inv12345", "path": "/r/inv12345", "originalUrl": "https://example.com/landing?activityId=1&inviter=2" }
  }
  ```

### 6.2 获取邀请好友列表（分页）

- Endpoint: `GET /api/v1/me/invited-friends`
- Query: `activityId`, `userId`, `page`（默认0）, `size`（默认20）
- 成功响应 (200 OK):

  ```json
  {
    "code": 200,
    "message": "success",
    "data": [ { "nickname": "用户10", "maskedPhone": "138****0010", "status": "registered" } ],
    "meta": {
      "pagination": {
        "page": 0,
        "size": 20,
        "total": 1,
        "totalPages": 1,
        "hasNext": false,
        "hasPrevious": false
      }
    }
  }
  ```

### 6.3 生成海报

- 图片：`GET /api/v1/me/poster/image`
- HTML：`GET /api/v1/me/poster/html`
- 配置：`GET /api/v1/me/poster/config`
- Query: `activityId`, `userId`, `template`（`template` 可选）
- 描述：图片/HTML 端点返回二进制或 HTML；配置端点返回 `ApiResponse<PosterConfigDto>`，`data` 包含 `template`、`imageUrl`、`htmlUrl`。

## 7. 分享跟踪 (Share Tracking)

### 7.1 创建分享跟踪

- **Endpoint**: `POST /api/v1/share/track`
- **描述**: 创建分享跟踪记录，用于追踪用户分享行为
- **请求体**: `application/json`

  ```json
  {
    "activityId": 1,
    "inviterUserId": 123,
    "source": "wechat",
    "utm": "campaign-spring"
  }
  ```

- **成功响应 (200 OK)**:

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {
      "trackingId": "track-abc123",
      "shortCode": "xyz789",
      "shareUrl": "https://example.com/r/xyz789",
      "activityId": 1,
      "inviterUserId": 123
    }
  }
  ```

### 7.2 获取分享指标

- **Endpoint**: `GET /api/v1/share/metrics`
- **描述**: 获取指定活动的分享统计指标
- **查询参数**:
  - `activityId` (必需): 活动ID
  - `startTime` (可选): 开始时间 (ISO 8601格式)
  - `endTime` (可选): 结束时间 (ISO 8601格式)

- **成功响应 (200 OK)**:

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {
      "activityId": 1,
      "totalClicks": 1500,
      "uniqueVisitors": 800,
      "sourceDistribution": {
        "wechat": 600,
        "weibo": 400,
        "direct": 200
      },
      "hourlyDistribution": {
        "0": 50,
        "1": 30,
        "2": 20
      },
      "startTime": "2025-03-01T00:00:00Z",
      "endTime": "2025-03-31T23:59:59Z"
    }
  }
  ```

### 7.3 获取顶级分享链接

- **Endpoint**: `GET /api/v1/share/top-links`
- **描述**: 获取分享次数最多的链接列表
- **查询参数**:
  - `activityId` (必需): 活动ID
  - `limit` (可选，默认10): 返回数量

- **成功响应 (200 OK)**:

  ```json
  {
    "code": 200,
    "message": "success",
    "data": [
      {
        "shortCode": "abc123",
        "clickCount": 500,
        "inviterUserId": 123
      },
      {
        "shortCode": "def456",
        "clickCount": 300,
        "inviterUserId": 456
      }
    ]
  }
  ```

### 7.4 获取转化漏斗

- **Endpoint**: `GET /api/v1/share/funnel`
- **描述**: 获取分享转化漏斗数据
- **查询参数**:
  - `activityId` (必需): 活动ID
  - `startTime` (可选): 开始时间
  - `endTime` (可选): 结束时间

- **成功响应 (200 OK)**:

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {
      "totalClicks": 1000,
      "withReferer": 800,
      "withUserAgent": 950,
      "refererRate": 0.8,
      "topReferers": {
        "google.com": 300,
        "facebook.com": 200,
        "twitter.com": 150
      }
    }
  }
  ```

### 7.5 获取分享元数据

- **Endpoint**: `GET /api/v1/share/share-meta`
- **描述**: 获取分享相关的元数据配置
- **查询参数**:
  - `activityId` (必需): 活动ID
  - `userId` (必需): 用户ID
  - `template` (可选，默认"default"): 模板名称

- **成功响应 (200 OK)**:

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {
      "title": "春季特惠活动",
      "description": "邀请好友，赢取大奖",
      "imageUrl": "https://example.com/poster.png",
      "shareUrl": "https://example.com/r/abc123"
    }
  }
  ```

### 7.6 注册分享来源

- **Endpoint**: `POST /api/v1/share/register-source`
- **描述**: 注册用户的分享来源渠道
- **请求体**: `application/json`

  ```json
  {
    "activityId": 1,
    "userId": 123,
    "channel": "wechat",
    "params": "utm_source=campaign1"
  }
  ```

- **成功响应 (200 OK)**:

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {
      "trackingId": "track-xyz",
      "shortCode": "abc789",
      "shareUrl": "https://example.com/r/abc789",
      "activityId": 1,
      "inviterUserId": 123
    }
  }
  ```

## 8. 回调管理 (Callbacks)

### 8.1 注册回调

- **Endpoint**: `POST /api/v1/callback/register`
- **描述**: 注册业务回调，用于接收活动相关事件通知
- **请求体**: `application/json`

  ```json
  {
    "activityId": 1,
    "callbackUrl": "https://your-domain.com/webhook",
    "events": ["user.registered", "user.invited", "reward.granted"],
    "secret": "your-webhook-secret"
  }
  ```

- **成功响应 (200 OK)**:

  ```json
  {
    "code": 200,
    "message": "success",
    "data": {
      "callbackId": "cb-123456",
      "activityId": 1,
      "callbackUrl": "https://your-domain.com/webhook",
      "status": "active"
    }
  }
  ```

- **回调事件格式**:

  ```json
  {
    "eventType": "user.registered",
    "eventId": "evt-abc123",
    "timestamp": "2025-03-01T10:00:00Z",
    "data": {
      "activityId": 1,
      "userId": 123,
      "inviterUserId": 456
    },
    "signature": "sha256-hash-of-payload"
  }
  ```

## 9. 用户奖励 (User Rewards)

### 9.1 获取用户奖励列表

- **Endpoint**: `GET /api/v1/me/rewards`
- **描述**: 获取当前用户的奖励记录（分页）
- **查询参数**:
  - `activityId` (必需): 活动ID
  - `userId` (必需): 用户ID
  - `page` (可选，默认0): 页码
  - `size` (可选，默认20): 每页数量

- **成功响应 (200 OK)**:

  ```json
  {
    "code": 200,
    "message": "success",
    "data": [
      {
        "type": "invite_reward",
        "points": 100,
        "createdAt": "2025-03-01T10:00:00Z"
      },
      {
        "type": "share_reward",
        "points": 50,
        "createdAt": "2025-03-02T15:30:00Z"
      }
    ],
    "meta": {
      "pagination": {
        "page": 0,
        "size": 20,
        "total": 2,
        "totalPages": 1,
        "hasNext": false,
        "hasPrevious": false
      }
    }
  }
  ```

## 10. 速率限制

所有API端点都受到速率限制保护：

- **默认限制**: 每分钟100次请求（基于API Key）
- **超出限制响应 (429 Too Many Requests)**:

  ```json
  {
    "code": 429,
    "message": "Rate limit exceeded",
    "error": {
      "message": "Too many requests, please try again later",
      "code": "RATE_LIMIT_EXCEEDED"
    }
  }
  ```

- **响应头**:
  - `X-RateLimit-Limit`: 速率限制值
  - `X-RateLimit-Remaining`: 剩余请求次数
  - `Retry-After`: 重试等待秒数

## 11. API版本控制

- **当前版本**: v1
- **版本指定**: 通过URL路径 `/api/v1/...`
- **版本协商**: 可通过 `X-API-Version` 请求头指定版本（可选）
- **响应头**: `X-API-Version` 返回实际使用的API版本

## 12. 最佳实践

### 12.1 错误处理

```javascript
try {
  const response = await fetch('/api/v1/activities/1', {
    headers: {
      'X-API-Key': 'your-api-key',
      'Authorization': 'Bearer your-token'
    }
  });
  
  const result = await response.json();
  
  if (result.code !== 200) {
    console.error('API Error:', result.error);
    // 处理业务错误
  }
} catch (error) {
  console.error('Network Error:', error);
  // 处理网络错误
}
```

### 12.2 分页处理

```javascript
async function fetchAllPages(activityId) {
  let page = 0;
  let allData = [];
  let hasNext = true;
  
  while (hasNext) {
    const response = await fetch(
      `/api/v1/activities/${activityId}/leaderboard?page=${page}&size=100`,
      { headers: { 'X-API-Key': 'your-key' } }
    );
    const result = await response.json();
    
    allData = allData.concat(result.data);
    hasNext = result.meta.pagination.hasNext;
    page++;
  }
  
  return allData;
}
```

### 12.3 速率限制处理

```javascript
async function apiCallWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);
    
    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After') || 60;
      await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      continue;
    }
    
    return response;
  }
  
  throw new Error('Max retries exceeded');
}
```

---

**文档版本**: 2.0
**最后更新**: 2026-03-04
**维护者**: 技术团队
