# Presence Service API 文档 ## 概述 Base URL: `/api/v1` 所有需要认证的接口都需要在请求头中携带 JWT Token: ``` Authorization: Bearer ``` --- ## 1. 在线状态 API ### 1.1 记录心跳 记录用户心跳,更新在线状态。 **请求** ``` POST /api/v1/presence/heartbeat Authorization: Bearer Content-Type: application/json ``` **请求体** | 字段 | 类型 | 必填 | 描述 | |-----|------|-----|------| | installId | string | 是 | 安装ID (8-64字符) | | appVersion | string | 是 | 应用版本号 | | clientTs | number | 是 | 客户端时间戳 (Unix秒) | **请求示例** ```json { "installId": "uuid-xxxx-xxxx-xxxx", "appVersion": "1.0.0", "clientTs": 1732685100 } ``` **响应** ```json { "ok": true, "serverTs": 1732685100123 } ``` | 字段 | 类型 | 描述 | |-----|------|------| | ok | boolean | 是否成功 | | serverTs | number | 服务器时间戳 (Unix毫秒) | **错误码** | HTTP 状态码 | 错误码 | 描述 | |------------|-------|------| | 400 | VALIDATION_ERROR | 参数校验失败 | | 401 | UNAUTHORIZED | 未授权 | --- ### 1.2 查询在线人数 查询当前在线用户数量。 **请求** ``` GET /api/v1/presence/online-count Authorization: Bearer ``` **查询参数** | 参数 | 类型 | 必填 | 默认值 | 描述 | |-----|------|-----|-------|------| | windowSeconds | number | 否 | 180 | 时间窗口 (秒) | **响应** ```json { "count": 12345, "windowSeconds": 180, "queriedAt": "2025-01-01T12:00:00.000Z" } ``` | 字段 | 类型 | 描述 | |-----|------|------| | count | number | 在线用户数 | | windowSeconds | number | 统计时间窗口 | | queriedAt | string | 查询时间 (ISO 8601) | --- ### 1.3 查询在线人数历史 查询指定时间范围内的在线人数历史快照。 **请求** ``` GET /api/v1/presence/online-history Authorization: Bearer ``` **查询参数** | 参数 | 类型 | 必填 | 默认值 | 描述 | |-----|------|-----|-------|------| | startTime | string | 是 | - | 开始时间 (ISO 8601) | | endTime | string | 是 | - | 结束时间 (ISO 8601) | | interval | string | 否 | 5m | 聚合间隔: 1m, 5m, 15m, 30m, 1h | **响应** ```json { "data": [ { "ts": "2025-01-01T12:00:00.000Z", "count": 12345 }, { "ts": "2025-01-01T12:05:00.000Z", "count": 12400 } ], "interval": "5m", "startTime": "2025-01-01T12:00:00.000Z", "endTime": "2025-01-01T13:00:00.000Z", "total": 12, "summary": { "max": 12500, "min": 12000, "avg": 12250 } } ``` | 字段 | 类型 | 描述 | |-----|------|------| | data | array | 快照数据列表 | | data[].ts | string | 快照时间 | | data[].count | number | 在线人数 | | interval | string | 聚合间隔 | | startTime | string | 实际开始时间 | | endTime | string | 实际结束时间 | | total | number | 数据点总数 | | summary.max | number | 最大在线人数 | | summary.min | number | 最小在线人数 | | summary.avg | number | 平均在线人数 | **错误码** | HTTP 状态码 | 错误码 | 描述 | |------------|-------|------| | 400 | INVALID_TIME_RANGE | 无效的时间范围 | | 400 | INVALID_INTERVAL | 无效的聚合间隔 | --- ## 2. 分析事件 API ### 2.1 批量上报事件 批量上报客户端分析事件。 **请求** ``` POST /api/v1/analytics/events Content-Type: application/json ``` **请求体** | 字段 | 类型 | 必填 | 描述 | |-----|------|-----|------| | events | array | 是 | 事件列表 | | events[].eventName | string | 是 | 事件名称 (字母开头,1-64字符) | | events[].installId | string | 是 | 安装ID | | events[].userId | string | 否 | 用户ID (已登录用户) | | events[].clientTs | number | 是 | 客户端时间戳 (Unix秒) | | events[].properties | object | 否 | 事件属性 | **请求示例** ```json { "events": [ { "eventName": "app_session_start", "installId": "uuid-xxxx-xxxx-xxxx", "userId": "12345", "clientTs": 1732685100, "properties": { "os": "iOS", "osVersion": "17.0", "appVersion": "1.0.0", "deviceModel": "iPhone 15 Pro", "province": "广东省", "city": "深圳市" } }, { "eventName": "presence_heartbeat", "installId": "uuid-xxxx-xxxx-xxxx", "clientTs": 1732685160 } ] } ``` **响应** ```json { "accepted": 2, "failed": 0, "errors": [] } ``` | 字段 | 类型 | 描述 | |-----|------|------| | accepted | number | 成功接收的事件数 | | failed | number | 失败的事件数 | | errors | array | 错误详情列表 | **预定义事件名称** | 事件名称 | 描述 | |---------|------| | app_session_start | 应用会话开始 | | app_session_end | 应用会话结束 | | presence_heartbeat | 心跳事件 | | user_login | 用户登录 | | user_logout | 用户登出 | --- ### 2.2 查询日活统计 查询指定日期范围的日活统计数据。 **请求** ``` GET /api/v1/analytics/dau Authorization: Bearer ``` **查询参数** | 参数 | 类型 | 必填 | 描述 | |-----|------|-----|------| | startDate | string | 是 | 开始日期 (YYYY-MM-DD) | | endDate | string | 是 | 结束日期 (YYYY-MM-DD) | **响应** ```json { "data": [ { "day": "2025-01-01", "dauCount": 50000, "dauByProvince": { "广东省": 15000, "北京市": 8000, "上海市": 7000 }, "dauByCity": { "深圳市": 10000, "北京市": 8000, "上海市": 7000 } } ], "total": 1, "summary": { "totalDau": 50000, "avgDau": 50000, "maxDau": 50000, "minDau": 50000 } } ``` | 字段 | 类型 | 描述 | |-----|------|------| | data | array | 日活数据列表 | | data[].day | string | 统计日期 | | data[].dauCount | number | 日活人数 | | data[].dauByProvince | object | 按省份统计 | | data[].dauByCity | object | 按城市统计 | | total | number | 数据天数 | | summary | object | 汇总统计 | **错误码** | HTTP 状态码 | 错误码 | 描述 | |------------|-------|------| | 400 | INVALID_DATE_FORMAT | 无效的日期格式 | | 400 | DATE_RANGE_TOO_LARGE | 日期范围过大 (最多90天) | --- ## 3. 健康检查 API ### 3.1 健康检查 检查服务健康状态。 **请求** ``` GET /api/v1/health ``` **响应** ```json { "status": "ok", "service": "presence-service", "timestamp": "2025-01-01T12:00:00.000Z" } ``` --- ## 4. 错误响应格式 所有错误响应遵循统一格式: ```json { "statusCode": 400, "path": "/api/v1/presence/heartbeat", "method": "POST", "message": "installId must be a string", "timestamp": "2025-01-01T12:00:00.000Z" } ``` | 字段 | 类型 | 描述 | |-----|------|------| | statusCode | number | HTTP 状态码 | | path | string | 请求路径 | | method | string | 请求方法 | | message | string | 错误信息 | | timestamp | string | 错误时间 | **ValidationPipe 错误格式** 当请求体校验失败时,message 字段可能为数组: ```json { "statusCode": 400, "path": "/api/v1/presence/heartbeat", "method": "POST", "message": [ "installId must be a string", "clientTs must be a number" ], "timestamp": "2025-01-01T12:00:00.000Z" } ``` --- ## 5. 通用 HTTP 状态码 | 状态码 | 描述 | |-------|------| | 200 | 请求成功 | | 201 | 资源创建成功 | | 400 | 请求参数错误 | | 401 | 未授权 | | 403 | 禁止访问 | | 404 | 资源不存在 | | 422 | 业务逻辑错误 | | 500 | 服务器内部错误 | --- ## 6. 限流策略 | 接口 | 限制 | 窗口 | |-----|------|-----| | POST /heartbeat | 60 次/分钟/用户 | 滑动窗口 | | POST /events | 100 次/分钟/IP | 滑动窗口 | | GET /online-count | 120 次/分钟/用户 | 滑动窗口 | | GET /online-history | 30 次/分钟/用户 | 滑动窗口 | | GET /dau | 30 次/分钟/用户 | 滑动窗口 | 超出限制返回 `429 Too Many Requests`。 --- ## 7. SDK 示例 ### JavaScript/TypeScript ```typescript class PresenceClient { private baseUrl: string; private token: string; constructor(baseUrl: string, token: string) { this.baseUrl = baseUrl; this.token = token; } async sendHeartbeat(installId: string, appVersion: string): Promise { const response = await fetch(`${this.baseUrl}/api/v1/presence/heartbeat`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.token}`, }, body: JSON.stringify({ installId, appVersion, clientTs: Math.floor(Date.now() / 1000), }), }); if (!response.ok) { throw new Error(`Heartbeat failed: ${response.status}`); } } async getOnlineCount(windowSeconds = 180): Promise { const response = await fetch( `${this.baseUrl}/api/v1/presence/online-count?windowSeconds=${windowSeconds}`, { headers: { 'Authorization': `Bearer ${this.token}`, }, } ); const data = await response.json(); return data.count; } } ``` ### cURL 示例 ```bash # 发送心跳 curl -X POST https://api.example.com/api/v1/presence/heartbeat \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "installId": "uuid-xxxx-xxxx-xxxx", "appVersion": "1.0.0", "clientTs": 1732685100 }' # 查询在线人数 curl https://api.example.com/api/v1/presence/online-count \ -H "Authorization: Bearer " # 查询在线历史 curl "https://api.example.com/api/v1/presence/online-history?startTime=2025-01-01T00:00:00Z&endTime=2025-01-01T12:00:00Z&interval=5m" \ -H "Authorization: Bearer " # 批量上报事件 curl -X POST https://api.example.com/api/v1/analytics/events \ -H "Content-Type: application/json" \ -d '{ "events": [ { "eventName": "app_session_start", "installId": "uuid-xxxx-xxxx-xxxx", "clientTs": 1732685100, "properties": {"os": "iOS"} } ] }' ```