# POST /api/v1/analytics/events — 批量上报埋点事件

> 所属:Analytics 组（前缀 `/api/v1/analytics`） | 鉴权:无（不强制登录，未登录态也要采集行为） | [← 返回 API 索引](../README.md)

批量接收新手引导（及后续）埋点，append 落 `analytics_event` 表。`user_id` 由客户端在 body 可选带上，不靠 Bearer。服务端补 `client_ip`（X-Forwarded-For）与 `server_at`（接收时间）。

## 入参

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `device_id` | string | ✅（≤64） | 设备 ID |
| `user_id` | int \| null | ❌ | 登录用户 ID（未登录可空） |
| `sent_at` | int \| null | ❌ | 批次发送时间（epoch ms） |
| `oem` | string \| null | ❌ | 厂商（如 `Xiaomi`） |
| `os` | string \| null | ❌ | 操作系统（如 `Android 14`） |
| `model` | string \| null | ❌ | 机型（如 `24115RA8EC`） |
| `app_ver` | string \| null | ❌ | App 版本 |
| `channel` | string \| null | ❌ | 渠道 |
| `events` | list[object] | ✅（1-200 条） | 事件数组 |
| `events[].event` | string | ✅（≤64） | 事件名（如 `onboarding_start`） |
| `events[].client_ts` | int | ✅ | 端事件发生时间（epoch ms） |
| `events[].session_id` | string \| null | ❌ | 会话 ID |
| `events[].page` | string \| null | ❌ | 页面标识 |
| `events[].network` | string \| null | ❌ | 网络类型（如 `wifi`） |
| `events[].props` | dict | ❌ | 事件属性（key-value） |

Mock 入参:
```json
{
  "device_id": "android_abc123def456",
  "user_id": 42,
  "sent_at": 1719993700000,
  "oem": "Xiaomi",
  "os": "Android 14",
  "model": "24115RA8EC",
  "app_ver": "0.1.5",
  "channel": "official",
  "events": [
    {
      "event": "onboarding_start",
      "client_ts": 1719993600000,
      "session_id": "sess_a1b2c3",
      "page": "onboarding",
      "network": "wifi",
      "props": {"step": "1", "source": "fresh_install"}
    },
    {
      "event": "onboarding_step_complete",
      "client_ts": 1719993615000,
      "session_id": "sess_a1b2c3",
      "page": "onboarding",
      "network": "wifi",
      "props": {"step": "1", "duration_ms": "15000"}
    }
  ]
}
```

## 出参

响应 `200`:`AnalyticsIngestOut`

| 字段 | 类型 | 说明 |
|---|---|---|
| `ok` | bool | 固定 `true` |
| `received` | int | 成功写入的条数 |

Mock 出参:
```json
{
  "ok": true,
  "received": 2
}
```

## 错误码
- `422` `events` 为空或超过 200 条 / 字段类型不符

## 说明
- `user_id` 不靠 JWT：未登录态也要采集行为（新手引导可能在登录前）
- 每批最多 200 条，建议客户端攒到一定量再批量上报
- `client_ts` 是端侧时间（客户端时钟），`server_at` 由服务端补（可靠时间轴）
