XuqmGroup-Server/xuqm-bugcollect-service/docs/BugCollect-API-v1.md

1438 行
40 KiB
Markdown

# BugCollect API v1 规范
> **版本**1.1.0
> **日期**2026-06-17
> **状态**Draft
> **设计参考**[Sentry Event Payloads](https://develop.sentry.dev/sdk/event-payloads/)
> **服务端模块**`xuqm-bugcollect-service`
> **基础路径**`/bugcollect/v1`
---
## 目录
1. [概述](#1-概述)
2. [术语表](#2-术语表)
3. [认证与安全](#3-认证与安全)
4. [数据上报端点](#4-数据上报端点)
- 4.1 [Issue 批量上报](#41-issue-批量上报)
- 4.2 [Event 批量上报](#42-event-批量上报)
- 4.3 [SourceMap / 符号文件上传](#43-sourcemap--符号文件上传)
- 4.4 [Session 上报](#44-session-上报)
5. [数据模型](#5-数据模型)
- 5.1 [批量信封 Envelope](#51-批量信封-envelope)
- 5.2 [IssueEvent](#52-issueevent)
- 5.3 [LogEvent](#53-logevent)
- 5.4 [Breadcrumb](#54-breadcrumb)
- 5.5 [枚举定义](#55-枚举定义)
6. [查询端点](#6-查询端点)
- 6.1 [Issue 列表](#61-issue-列表)
- 6.2 [Issue 详情](#62-issue-详情)
- 6.3 [Issue 事件列表](#63-issue-事件列表)
- 6.4 [Issue 趋势](#64-issue-趋势)
- 6.5 [Event 列表](#65-event-列表)
- 6.6 [概览统计](#66-概览统计)
- 6.7 [频率排行](#67-频率排行)
- 6.8 [风险排行](#68-风险排行)
- 6.9 [漏斗分析](#69-漏斗分析)
7. [Issue 管理端点](#7-issue-管理端点)
- 7.1 [解决 Issue](#71-解决-issue)
- 7.2 [忽略 Issue](#72-忽略-issue)
- 7.3 [分配责任人](#73-分配责任人)
- 7.4 [批量操作](#74-批量操作)
8. [Webhook](#8-webhook)
9. [错误码](#9-错误码)
10. [Fingerprint 去重机制](#10-fingerprint-去重机制)
11. [SDK 集成指南](#11-sdk-集成指南)
12. [数据库 Schema](#12-数据库-schema)
13. [变更日志](#13-变更日志)
---
## 1. 概述
BugCollect 是一个轻量级错误收集与分析服务,设计目标对齐 [Sentry](https://sentry.io) 核心能力,为 XuqmGroup 全平台 SDKAndroid、iOS、HarmonyOS、React Native、Web提供统一的错误上报、聚合、查询和告警能力。
### 核心能力
| 能力 | 说明 |
|------|------|
| **Issue 聚合** | 按 fingerprint 自动去重,将同类错误聚合为 Issue |
| **Event 追踪** | 每次错误发生记录独立 Event,保留完整上下文和面包屑 |
| **Analytics 事件** | 自定义业务事件上报,支持漏斗分析 |
| **Session 追踪** | 会话生命周期上报,支持 crash-free session rate 计算 |
| **符号化** | 上传 SourceMap / ProGuard mapping / dSYM,支持多平台堆栈还原 |
| **Webhook 告警** | 新 Issue、fatal 级别、阈值超限等事件触发 Webhook 通知 |
| **Issue 管理** | 解决、忽略、分配、批量操作 |
| **多平台** | 统一 API,平台差异由 `platform` 字段区分 |
### 设计原则
1. **Sentry 对齐**:字段命名和结构参考 Sentry,降低学习成本
2. **批量优先**:上报端点均为批量接口,减少网络请求
3. **信封封装**请求体使用信封Envelope结构,支持携带 SDK 元信息
4. **幂等安全**:每条 Event 携带 `eventId`,服务端按此去重
5. **向前兼容**:未知字段忽略不报错,新字段可选添加
---
## 2. 术语表
| 术语 | 说明 |
|------|------|
| **Issue** | 一类错误的聚合,由 fingerprint 唯一标识。一个 Issue 对应多次 Event |
| **Event** | 一次具体的错误发生记录,包含完整的堆栈、面包屑、设备、用户等上下文 |
| **Analytics Event** | 自定义业务事件(如页面访问、按钮点击),用于漏斗分析 |
| **Session** | 一次应用使用过程,从前台打开到退出/崩溃,用于计算 crash-free rate |
| **Breadcrumb** | 崩溃前的操作轨迹,如导航、网络请求、用户操作,自动附带在 Event 中 |
| **Fingerprint** | 错误指纹,SHA-256 哈希值,用于 Issue 去重分组 |
| **Level** | 错误严重级别:`fatal` > `error` > `warning` > `info` > `debug` |
| **Status** | Issue 状态:`open`(未处理)/ `resolved`(已解决)/ `ignored`(已忽略) |
| **Release** | 应用版本号,对应 `appVersion` |
| **Environment** | 运行环境:`production` / `staging` / `development` / `integration` |
| **Envelope** | 批量请求的信封结构,包含 `sentAt`、`sdk`、`events` |
| **AppKey** | 应用唯一标识,由平台分配 |
---
## 3. 认证与安全
### 上报端点SDK → 服务端)
SDK 通过 `config.xuqm` 完成初始化认证,`appKey` 由 SDK 自动从配置文件读取并附带在请求体中,**开发者无需手动处理认证逻辑**。
```
appKey 来源assets/xuqm/config.xuqm加密文件,由租户平台下发
```
认证链路:
1. 开发者从租户平台下载 `config.xuqm`,内含 `appKey` 和可选的 `serverUrl`
2. `config.xuqm` 已加密存储,且绑定包名SDK 启动时校验包名与文件中记录的一致性)
3. SDK 初始化时从平台拉取配置,验证 BugCollect 服务已开通(`t_feature_service.enabled = true`);未开通时 `captureError` 为空操作,不发送任何请求
4. 服务端收到上报后,验证 `appKey` 有效且对应 BugCollect 服务已激活,否则拒绝写入
> **服务激活即权限边界**BugCollect 服务开通状态由租户在平台管理,未开通的 `appKey` 的上报请求会被服务端拒绝,无需额外 Token。
建议 SDK 端通过 **HTTPS** 传输,防止 appKey 被中间人截获。
### 查询端点 / 管理端点Web → 服务端)
- 通过 API 网关统一鉴权(租户 Token
- 查询参数中的 `appKey` 必须与鉴权租户匹配
### 速率限制
| 端点类型 | 限制 | 超限行为 |
|---------|------|---------|
| Issue / Event 上报 | 1000 次/分钟/appKey | HTTP 429,SDK 将事件重新入队,下次重试 |
| Session 上报 | 5000 次/分钟/appKey | HTTP 429,SDK 丢弃(会话数据可接受损失) |
| SourceMap 上传 | 20 次/小时/appKey | HTTP 429 |
| 查询端点 | 100 次/分钟/租户 | HTTP 429 |
---
## 4. 数据上报端点
### 4.1 Issue 批量上报
捕获的错误、未捕获的崩溃统一通过此端点上报。
```
POST /bugcollect/v1/issues/batch
Content-Type: application/json
```
#### 请求体
```json
{
"sentAt": "2026-06-17T10:30:00Z",
"sdk": {
"name": "bugcollect.android",
"version": "1.0.0"
},
"events": [
{
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"appKey": "2000111111110002",
"level": "error",
"platform": "android",
"release": "7.2.13",
"environment": "production",
"timestamp": 1718620200000,
"fingerprint": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456",
"exception": {
"type": "IllegalStateException",
"value": "更新服务未开通 (code=40404)",
"stacktrace": "java.lang.IllegalStateException: 更新服务未开通\n\tat com.szyx.app.MainActivity.onCreate(MainActivity.kt:178)\n\tat android.app.Activity.performCreate(Activity.java:8000)"
},
"breadcrumbs": [
{
"timestamp": 1718620195000,
"category": "navigation",
"message": "进入首页",
"level": "info"
},
{
"timestamp": 1718620199000,
"category": "network",
"message": "GET /api/update → 404",
"level": "warning",
"data": { "url": "https://update.dev.xuqinmin.com/", "statusCode": 404 }
}
],
"user": {
"id": "user-123"
},
"device": {
"name": "Pixel 7",
"model": "Pixel 7",
"manufacturer": "Google",
"osName": "Android",
"osVersion": "14",
"locale": "zh_CN",
"timezone": "Asia/Shanghai",
"network": "wifi",
"isEmulator": false,
"freeMemoryMb": 512,
"buildType": "release"
},
"tags": {
"context": "checkAppUpdate"
}
}
]
}
```
#### 响应
```json
{
"code": 200,
"message": "success",
"data": null
}
```
> **幂等说明**:服务端按 `eventId` 去重。相同 `appKey` + `eventId` 的二次提交直接返回 200,不重复写入。
#### cURL 示例
```bash
curl -X POST https://bugcollect.xuqinmin.com/bugcollect/v1/issues/batch \
-H "Content-Type: application/json" \
-d '{
"sentAt": "2026-06-17T10:30:00Z",
"sdk": { "name": "bugcollect.android", "version": "1.0.0" },
"events": [{
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"appKey": "2000111111110002",
"level": "error",
"platform": "android",
"release": "7.2.13",
"environment": "production",
"timestamp": 1718620200000,
"fingerprint": "abc123...",
"exception": {
"type": "NullPointerException",
"value": "Attempt to invoke virtual method on a null reference",
"stacktrace": "java.lang.NullPointerException: ...\n\tat ..."
}
}]
}'
```
---
### 4.2 Event 批量上报
自定义业务事件(页面访问、功能使用、漏斗步骤等)通过此端点上报。
```
POST /bugcollect/v1/events/batch
Content-Type: application/json
```
#### 请求体
```json
{
"sentAt": "2026-06-17T10:30:00Z",
"sdk": {
"name": "bugcollect.android",
"version": "1.0.0"
},
"events": [
{
"eventId": "660e8400-e29b-41d4-a716-446655440001",
"appKey": "2000111111110002",
"name": "page_view",
"properties": {
"page": "/home",
"referrer": "/login"
},
"platform": "android",
"release": "7.2.13",
"environment": "production",
"timestamp": 1718620200000,
"user": {
"id": "user-123"
},
"device": {
"osName": "Android",
"osVersion": "14",
"isEmulator": false
}
}
]
}
```
#### 响应
```json
{
"code": 200,
"message": "success",
"data": null
}
```
---
### 4.3 SourceMap / 符号文件上传
上传符号文件,用于堆栈还原。各平台文件格式不同:
| 平台 | 文件类型 | 说明 |
|------|----------|------|
| `web` / `react-native` | `.map` | JavaScript SourceMap |
| `android` | `.txt` | ProGuard / R8 mapping 文件 |
| `ios` | `.zip` | dSYM 压缩包(含 DWARF 调试信息) |
| `harmonyos` | `.map` | 类 SourceMap 格式 |
```
POST /bugcollect/v1/sourcemaps/upload
Content-Type: multipart/form-data
```
#### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `appKey` | string | ✅ | 应用标识 |
| `platform` | string | ✅ | 平台:`web` / `react-native` / `android` / `ios` / `harmonyos` |
| `appVersion` | string | ✅ | 应用版本,对应 `release` 字段 |
| `bundleName` | string | | JS Bundle 名称,默认 `index`web/rn 专用) |
| `file` | file | ✅ | 符号文件 |
#### cURL 示例
```bash
# React Native SourceMap
curl -X POST https://bugcollect.xuqinmin.com/bugcollect/v1/sourcemaps/upload \
-F "appKey=2000111111110002" \
-F "platform=react-native" \
-F "appVersion=7.2.13" \
-F "bundleName=index" \
-F "file=@./index.bundle.map"
# Android ProGuard mapping
curl -X POST https://bugcollect.xuqinmin.com/bugcollect/v1/sourcemaps/upload \
-F "appKey=2000111111110002" \
-F "platform=android" \
-F "appVersion=7.2.13" \
-F "file=@./mapping.txt"
# iOS dSYM
curl -X POST https://bugcollect.xuqinmin.com/bugcollect/v1/sourcemaps/upload \
-F "appKey=2000111111110002" \
-F "platform=ios" \
-F "appVersion=7.2.13" \
-F "file=@./YwxApp.app.dSYM.zip"
```
#### 响应
```json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"appKey": "2000111111110002",
"platform": "android",
"appVersion": "7.2.13",
"uploadedAt": "2026-06-17T10:30:00"
}
}
```
---
### 4.4 Session 上报
上报会话生命周期,用于计算 crash-free session rate。
```
POST /bugcollect/v1/sessions/batch
Content-Type: application/json
```
#### 请求体
```json
{
"sentAt": "2026-06-17T10:30:00Z",
"sdk": { "name": "bugcollect.android", "version": "1.0.0" },
"sessions": [
{
"sessionId": "sess-abc-123",
"appKey": "2000111111110002",
"status": "exited",
"platform": "android",
"release": "7.2.13",
"environment": "production",
"startedAt": 1718620100000,
"duration": 95000,
"user": { "id": "user-123" },
"errors": 0
}
]
}
```
#### Session 状态
| `status` | 说明 |
|---------|------|
| `ok` | 进行中(心跳上报) |
| `exited` | 正常退出(用户主动离开或 App 进入后台超时) |
| `crashed` | 因未捕获异常终止 |
| `abnormal` | 进程被系统杀死ANR、OOM、后台清理 |
#### 字段说明
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `sessionId` | string | ✅ | 会话唯一 IDUUID |
| `appKey` | string | ✅ | 应用标识 |
| `status` | string | ✅ | 会话状态,见上表 |
| `platform` | string | ✅ | 平台 |
| `release` | string | | 应用版本 |
| `environment` | string | | 运行环境 |
| `startedAt` | long | ✅ | 会话开始时间戳(毫秒) |
| `duration` | long | | 会话时长(毫秒) |
| `user.id` | string | | 用户 ID |
| `errors` | int | | 会话内捕获的 error 数量 |
---
## 5. 数据模型
### 5.1 批量信封 Envelope
所有批量上报端点共享统一的信封结构。信封层的 `sdk` 字段作为默认值,事件层不再重复携带。
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `sentAt` | string (ISO 8601) | | 客户端发送时间,用于时钟偏差校正 |
| `sdk` | object | | SDK 信息 |
| `sdk.name` | string | | SDK 标识,如 `bugcollect.android`、`bugcollect.ios`、`bugcollect.harmonyos`、`bugcollect.rn`、`bugcollect.web` |
| `sdk.version` | string | | SDK 版本号 |
| `events` | array | ✅ | 事件数组,不能为空 |
---
### 5.2 IssueEvent
错误事件数据模型,用于 `captureError()``captureCrash()`
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `eventId` | string (UUID) | ✅ | 客户端生成的唯一事件 ID,服务端按此去重 |
| `appKey` | string | ✅ | 应用标识 |
| `level` | string | ✅ | 严重级别,见 [Level 枚举](#551-level) |
| `platform` | string | ✅ | 平台,见 [Platform 枚举](#552-platform) |
| `fingerprint` | string | ✅ | 错误指纹,64 字符 SHA-256 哈希 |
| `timestamp` | long | ✅ | 事件时间戳(毫秒) |
| `exception` | object | | 异常信息 |
| `exception.type` | string | | 异常类名,如 `NullPointerException` |
| `exception.value` | string | | 异常消息 |
| `exception.stacktrace` | string | | 完整堆栈文本 |
| `breadcrumbs` | array | | 崩溃前操作轨迹,见 [Breadcrumb](#54-breadcrumb),最多 50 条 |
| `release` | string | | 应用版本号 |
| `environment` | string | | 运行环境,默认 `production` |
| `user` | object | | 用户信息 |
| `user.id` | string | | 用户 ID |
| `device` | object | | 设备信息,见下表 |
| `tags` | object | | 自定义标签,key-value 均为 string |
#### device 字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `name` | string | 设备名称,如 `Pixel 7` |
| `model` | string | 设备型号 |
| `manufacturer` | string | 制造商 |
| `osName` | string | 操作系统名称 |
| `osVersion` | string | 操作系统版本 |
| `locale` | string | 语言区域,如 `zh_CN` |
| `timezone` | string | 时区,如 `Asia/Shanghai` |
| `network` | string | 网络类型:`wifi` / `cellular` / `none` / `unknown` |
| `isEmulator` | boolean | 是否模拟器(过滤开发噪声) |
| `freeMemoryMb` | int | 可用内存MB |
| `buildType` | string | 构建类型:`debug` / `release` |
#### 最小上报示例
```json
{
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"appKey": "2000111111110002",
"level": "error",
"platform": "android",
"fingerprint": "abc123...",
"timestamp": 1718620200000,
"exception": {
"type": "Exception",
"value": "something went wrong",
"stacktrace": "..."
}
}
```
---
### 5.3 LogEvent
自定义业务事件数据模型,用于 `event()` 方法。
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `eventId` | string (UUID) | ✅ | 客户端生成的唯一事件 ID,服务端按此去重 |
| `appKey` | string | ✅ | 应用标识 |
| `name` | string | ✅ | 事件名称,如 `page_view`、`button_click` |
| `timestamp` | long | ✅ | 事件时间戳(毫秒) |
| `properties` | object | | 事件属性,任意 key-value |
| `platform` | string | | 平台 |
| `release` | string | | 应用版本号 |
| `environment` | string | | 运行环境 |
| `user` | object | | 用户信息 |
| `user.id` | string | | 用户 ID |
| `device` | object | | 设备信息(同 IssueEvent.device |
---
### 5.4 Breadcrumb
崩溃前的操作轨迹条目,自动附带在 IssueEvent 的 `breadcrumbs` 数组中,最多保留最近 50 条。
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `timestamp` | long | ✅ | 轨迹时间戳(毫秒) |
| `category` | string | ✅ | 分类,见下表 |
| `message` | string | ✅ | 描述文本 |
| `level` | string | | `info` / `warning` / `error`,默认 `info` |
| `data` | object | | 额外键值对,如 `url`、`statusCode`、`method` |
#### 标准 Category
| category | 说明 | 典型 data 字段 |
|----------|------|---------------|
| `navigation` | 页面/路由切换 | `from`, `to` |
| `network` | HTTP 请求 | `url`, `method`, `statusCode`, `durationMs` |
| `ui` | 用户交互 | `action`tap/swipe,`target` |
| `lifecycle` | App / Activity 生命周期 | `event`foreground/background |
| `console` | 手动打点日志 | — |
| `error` | SDK 内部捕获的非致命错误 | — |
---
### 5.5 枚举定义
#### 5.5.1 Level
错误严重级别,从高到低排列。
| 值 | 含义 | 对应 SDK 方法 | Web 展示颜色 |
|----|------|--------------|-------------|
| `fatal` | 未捕获崩溃,应用即将终止 | `captureCrash()` | 🔴 深红 |
| `error` | 捕获的错误,不影响应用存活 | `captureError()` | 🔴 红 |
| `warning` | 警告,潜在问题 | `warn()` | 🟡 黄 |
| `info` | 信息性记录 | `info()` | 🔵 蓝 |
| `debug` | 调试信息 | `debug()` | ⚪ 灰 |
**服务端存储策略**
- `fatal` / `error` → 写入 `log_issues` 表参与 Issue 聚合,同时写入 `log_issue_events` 表记录明细
- `warning` / `info` / `debug`**仅写入** `log_issue_events` 表,不创建或更新 Issue,不触发告警
#### 5.5.2 Platform
| 值 | 说明 |
|----|------|
| `android` | Android 原生 |
| `ios` | iOS 原生 |
| `harmonyos` | HarmonyOS 原生 |
| `react-native` | React Native 跨平台 |
| `web` | Web 浏览器 |
| `java-server` | Java 服务端 |
| `python-server` | Python 服务端 |
| `go-server` | Go 服务端 |
#### 5.5.3 Environment
| 值 | 说明 |
|----|------|
| `production` | 生产环境(默认) |
| `staging` | 预发布环境 |
| `integration` | 集成测试环境 |
| `development` | 开发环境 |
#### 5.5.4 Issue Status
| 值 | 说明 |
|----|------|
| `open` | 未处理(默认) |
| `resolved` | 已解决 |
| `ignored` | 已忽略,不再告警 |
---
## 6. 查询端点
> 以下端点均为 `GET` 请求,返回数据格式统一为 `ApiResponse<T>`。
### 通用响应格式
```json
{
"code": 200,
"message": "success",
"data": { ... }
}
```
### 通用分页格式
```json
{
"items": [],
"page": 1,
"size": 20,
"total": 100
}
```
---
### 6.1 Issue 列表
```
GET /bugcollect/v1/issues
```
#### 查询参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `appKey` | string | ✅ | | 应用标识 |
| `level` | string | | | 级别筛选:`fatal` / `error` / `warning` |
| `status` | string | | `open` | 状态筛选:`open` / `resolved` / `ignored` |
| `platform` | string | | | 平台筛选 |
| `release` | string | | | 版本筛选 |
| `q` | string | | | 按 Issue 标题全文搜索 |
| `from` | string | | | 起始日期 `YYYY-MM-DD` |
| `to` | string | | | 结束日期 `YYYY-MM-DD` |
| `page` | int | | 1 | 页码(从 1 开始) |
| `size` | int | | 20 | 每页条数 |
#### 响应
```json
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": 1,
"appKey": "2000111111110002",
"fingerprint": "abc123...",
"level": "error",
"status": "open",
"title": "IllegalStateException: 更新服务未开通",
"firstSeenAt": "2026-06-17T10:00:00",
"lastSeenAt": "2026-06-17T12:00:00",
"count": 15,
"affectedUsers": 8,
"platform": "android",
"release": "7.2.13"
}
],
"page": 1,
"size": 20,
"total": 1
}
}
```
---
### 6.2 Issue 详情
仅返回 Issue 元数据。事件明细通过 [6.3 Issue 事件列表](#63-issue-事件列表) 分页获取。
```
GET /bugcollect/v1/issues/{id}
```
#### 路径参数
| 参数 | 类型 | 说明 |
|------|------|------|
| `id` | long | Issue ID |
#### 响应
```json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"appKey": "2000111111110002",
"fingerprint": "abc123...",
"level": "error",
"status": "open",
"title": "IllegalStateException: 更新服务未开通",
"firstSeenAt": "2026-06-17T10:00:00",
"lastSeenAt": "2026-06-17T12:00:00",
"count": 15,
"affectedUsers": 8,
"platform": "android",
"release": "7.2.13",
"assignee": null
}
}
```
---
### 6.3 Issue 事件列表
分页获取某个 Issue 下的所有事件明细,含完整堆栈、面包屑、设备信息。
```
GET /bugcollect/v1/issues/{id}/events
```
#### 查询参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `id` | long | ✅(路径) | | Issue ID |
| `from` | string | | | 起始日期 |
| `to` | string | | | 结束日期 |
| `page` | int | | 1 | 页码 |
| `size` | int | | 20 | 每页条数 |
#### 响应
```json
{
"code": 200,
"message": "success",
"data": {
"items": [
{
"id": 101,
"issueId": 1,
"eventId": "550e8400-e29b-41d4-a716-446655440000",
"appKey": "2000111111110002",
"level": "error",
"userId": "user-123",
"sessionId": "sess-abc",
"exception": {
"type": "IllegalStateException",
"value": "更新服务未开通",
"stacktrace": "java.lang.IllegalStateException: ...",
"stackSymbolicated": null
},
"breadcrumbs": [
{
"timestamp": 1718620195000,
"category": "navigation",
"message": "进入首页",
"level": "info"
}
],
"tags": { "context": "checkAppUpdate" },
"device": {
"osName": "Android",
"osVersion": "14",
"isEmulator": false,
"network": "wifi"
},
"platform": "android",
"release": "7.2.13",
"environment": "production",
"sdkName": "bugcollect.android",
"sdkVersion": "1.0.0",
"createdAt": "2026-06-17T10:30:00"
}
],
"page": 1,
"size": 20,
"total": 15
}
}
```
---
### 6.4 Issue 趋势
获取某个 Issue 在指定时间范围内的每日发生次数趋势,用于判断问题是在收敛还是扩散。
```
GET /bugcollect/v1/issues/{id}/trend
```
#### 查询参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `from` | string | | 近 14 天 | 起始日期 `YYYY-MM-DD` |
| `to` | string | | 今天 | 结束日期 `YYYY-MM-DD` |
#### 响应
```json
{
"code": 200,
"message": "success",
"data": {
"issueId": 1,
"points": [
{ "date": "2026-06-11", "count": 2, "affectedUsers": 1 },
{ "date": "2026-06-12", "count": 5, "affectedUsers": 3 },
{ "date": "2026-06-17", "count": 8, "affectedUsers": 5 }
]
}
}
```
---
### 6.5 Event 列表
```
GET /bugcollect/v1/events
```
#### 查询参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `appKey` | string | ✅ | | 应用标识 |
| `name` | string | | | 事件名称筛选 |
| `userId` | string | | | 用户 ID 筛选 |
| `from` | string | | | 起始日期 |
| `to` | string | | | 结束日期 |
| `page` | int | | 1 | 页码 |
| `size` | int | | 20 | 每页条数 |
---
### 6.6 概览统计
```
GET /bugcollect/v1/overview?appKey={appKey}
```
#### 查询参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `appKey` | string | ✅ | 应用标识 |
| `from` | string | | 起始日期 |
| `to` | string | | 结束日期 |
#### 响应
```json
{
"code": 200,
"message": "success",
"data": {
"totalIssues": 42,
"openIssues": 35,
"todayNewIssues": 3,
"affectedUsers": 128,
"crashFreeSessionRate": 0.9971,
"crashRate": 0.05,
"crashRateTrend": [
{ "date": "2026-06-11", "count": 5, "rate": 0.03 },
{ "date": "2026-06-12", "count": 8, "rate": 0.05 }
],
"topIssues": [
{
"id": 1,
"title": "NullPointerException: ...",
"level": "error",
"count": 15,
"affectedUsers": 8
}
]
}
}
```
> **crashFreeSessionRate**`1 - (crashed_sessions / total_sessions)`,行业标准指标,需 [Session 上报](#44-session-上报) 数据支撑。
---
### 6.7 频率排行
```
GET /bugcollect/v1/issues/rankings/frequency
```
#### 查询参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `appKey` | string | ✅ | | 应用标识 |
| `from` | string | | | 起始日期 |
| `to` | string | | | 结束日期 |
| `limit` | int | | 20 | 返回条数 |
---
### 6.8 风险排行
```
GET /bugcollect/v1/issues/rankings/risk
```
风险分 = `count × level_weight × recency_weight`,`fatal` 权重最高。
#### 查询参数
同 [频率排行](#67-频率排行)。
---
### 6.9 漏斗分析
```
GET /bugcollect/v1/events/funnel
```
#### 查询参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `appKey` | string | ✅ | 应用标识 |
| `steps` | string | ✅ | 逗号分隔的步骤名,如 `page_view,click_submit,order_created` |
| `from` | string | | 起始日期 |
| `to` | string | | 结束日期 |
#### 响应
```json
{
"code": 200,
"message": "success",
"data": {
"steps": ["page_view", "click_submit", "order_created"],
"counts": [1000, 600, 300],
"rates": [100.0, 60.0, 30.0]
}
}
```
---
## 7. Issue 管理端点
> 以下端点均需租户 Token 鉴权Web 端调用)。
### 7.1 解决 Issue
```
PUT /bugcollect/v1/issues/{id}/resolve
```
将 Issue 状态置为 `resolved`。若该 Issue 后续有新 Event 上报,状态自动回滚到 `open` 并触发 `issue.regressed` Webhook。
#### 响应
```json
{ "code": 200, "message": "success", "data": null }
```
---
### 7.2 忽略 Issue
```
PUT /bugcollect/v1/issues/{id}/ignore
```
将 Issue 状态置为 `ignored`。忽略后不再触发告警,不影响数据记录。
---
### 7.3 分配责任人
```
PUT /bugcollect/v1/issues/{id}/assign
Content-Type: application/json
```
#### 请求体
```json
{ "assignee": "user@example.com" }
```
`null` 取消分配。
---
### 7.4 批量操作
```
POST /bugcollect/v1/issues/bulk
Content-Type: application/json
```
#### 请求体
```json
{
"ids": [1, 2, 3],
"action": "resolve"
}
```
#### action 枚举
| 值 | 说明 |
|----|------|
| `resolve` | 批量解决 |
| `ignore` | 批量忽略 |
| `reopen` | 批量重新打开 |
---
## 8. Webhook
### 8.1 查询 Webhook 列表
```
GET /bugcollect/v1/webhooks?appKey={appKey}
```
### 8.2 创建 Webhook
```
POST /bugcollect/v1/webhooks
Content-Type: application/json
```
#### 请求体
```json
{
"appKey": "2000111111110002",
"url": "https://hooks.example.com/notify",
"events": ["issue.created", "issue.fatal"],
"secret": "webhook-secret-key"
}
```
### 8.3 更新 Webhook
```
PUT /bugcollect/v1/webhooks/{id}
```
### 8.4 删除 Webhook
```
DELETE /bugcollect/v1/webhooks/{id}
```
### Webhook 事件类型
| 事件 | 触发时机 |
|------|---------|
| `issue.created` | 新 Issue 首次出现 |
| `issue.fatal` | `fatal` 级别事件上报时立即触发(不等去重) |
| `issue.threshold` | Issue 发生次数超过配置阈值(默认 100 次) |
| `issue.regressed` | 已解决的 Issue 重新出现 |
| `issue.resolved` | Issue 被标记为已解决(用于 CI/CD 集成) |
### Webhook 回调体
```json
{
"event": "issue.created",
"timestamp": "2026-06-17T10:30:00Z",
"issue": {
"id": 1,
"appKey": "2000111111110002",
"level": "error",
"status": "open",
"title": "IllegalStateException: 更新服务未开通",
"platform": "android",
"count": 1,
"affectedUsers": 1,
"firstSeenAt": "2026-06-17T10:30:00"
}
}
```
> **签名验证**Webhook 请求携带 `X-BugCollect-Signature: sha256=<hmac>` 请求头,接收方用 `secret` 验证请求合法性。
---
## 9. 错误码
| HTTP 状态码 | code | 说明 |
|------------|------|------|
| 200 | 200 | 成功 |
| 400 | 400 | 请求参数校验失败 |
| 400 | 40001 | `appKey` 不能为空 |
| 400 | 40002 | `events` / `sessions` 数组为空 |
| 400 | 40003 | `fingerprint` 格式非法(非 64 字符十六进制) |
| 400 | 40004 | `level` 不在枚举范围内 |
| 400 | 40005 | `platform` 不在枚举范围内 |
| 403 | 40301 | `appKey` 对应的 BugCollect 服务未开通 |
| 404 | 40401 | Issue 不存在 |
| 429 | 42901 | 请求频率超限 |
| 500 | 500 | 服务端内部错误 |
---
## 10. Fingerprint 去重机制
### 默认算法
SDK 端默认使用以下公式生成 fingerprint注意不含 `level`,同一错误不同严重程度归为同一 Issue
```
SHA-256( normalize(exception.type + ":" + exception.value) + ":" + top_3_stack_lines )
```
- `normalize` — 移除数字字面量、UUID、URL、内存地址等易变内容
- `top_3_stack_lines` — 取堆栈中前 3 行(跳过 SDK 内部框架层)
### 自定义 Fingerprint
SDK 端可传入自定义 fingerprint,覆盖默认算法
```kotlin
BugCollect.captureError(exception, tags = mapOf("context" to "checkAppUpdate"), fingerprint = "custom-group-key")
```
### 分组优先级
1. **自定义 fingerprint** — 如果 SDK 传入,完全按此分组
2. **堆栈分组** — 按 `exception.type` + 归一化堆栈前 3 行
3. **消息分组** — 无堆栈时按 `exception.type` + `exception.value` 分组
### 混淆代码处理
Android Release 包经 ProGuard / R8 混淆后,堆栈行类名如 `a.b.c.d` 无法直接分组。建议:
1. 上传 [ProGuard mapping 文件](#43-sourcemap--符号文件上传)
2. 服务端符号化后将还原堆栈写入 `stack_symbolicated`
3. 对已符号化的堆栈重新计算 fingerprint服务端异步任务
---
## 11. SDK 集成指南
### 11.1 Android SDK
#### 初始化
SDK 通过 `config.xuqm` 自动初始化,无需手动调用初始化代码。DSN Token 已内含于配置文件。
```kotlin
// 无需手动初始化,ContentProvider 自动完成
// 若需手动初始化:
XuqmSDK.initialize(context, appKey = "2000111111110002")
```
#### 设置用户
通过 `XuqmSDK` 统一管理用户信息,BugCollect 自动跟随:
```kotlin
XuqmSDK.setUserInfo(XuqmUserInfo(userId = "user-123"))
```
#### 捕获错误
```kotlin
try {
// 业务代码
} catch (e: Exception) {
BugCollect.captureError(e, tags = mapOf("context" to "checkAppUpdate"))
}
```
#### 记录面包屑
```kotlin
BugCollect.addBreadcrumb(category = "navigation", message = "进入首页")
BugCollect.addBreadcrumb(
category = "network",
message = "GET /api/update → 404",
level = "warning",
data = mapOf("url" to url, "statusCode" to 404)
)
```
#### 记录事件
```kotlin
BugCollect.event("page_view", properties = mapOf("page" to "/home"))
```
#### 设置环境
```kotlin
BugCollect.setEnvironment("staging")
```
### 11.2 React Native SDK
```typescript
import { BugCollect } from '@xuqm/rn-bugcollect';
// 捕获错误
try { ... } catch (e) {
BugCollect.captureError(e, { tags: { context: 'checkAppUpdate' } });
}
// 记录面包屑
BugCollect.addBreadcrumb({ category: 'navigation', message: '进入首页' });
// 记录事件
BugCollect.event('page_view', { page: '/home' });
```
### 11.3 Web SDK
```javascript
import { BugCollect } from '@xuqm/bugcollect-web';
BugCollect.init({ appKey: '2000111111110002' });
// 自动捕获未处理异常window.onerror / unhandledrejection
// 手动捕获
try { ... } catch (e) {
BugCollect.captureError(e);
}
```
### 11.4 批量上报策略
SDK 内部自动管理队列,开发者无需关心。
| 参数 | 值 | 说明 |
|------|------|------|
| 批量大小 | 30 条 | 积攒 30 条后立即发送 |
| 定时刷新 | 10 秒 | 未满 30 条时每 10 秒发送一次 |
| 最大队列 | 500 条 | 超出后丢弃最旧的 |
| 持久化范围 | `fatal` / `error` | 发送失败写入磁盘,下次启动补发;`warning` 及以下仅内存队列 |
| 重试 | 1 次 | 发送失败重试 1 次,失败后重新入队 |
| 幂等保障 | `eventId` | 每条 Event 携带 UUID,服务端按此去重,重试不会产生重复数据 |
---
## 12. 数据库 Schema
### log_issues — Issue 聚合表
```sql
CREATE TABLE log_issues (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
app_key VARCHAR(64) NOT NULL,
fingerprint VARCHAR(128) NOT NULL,
level VARCHAR(20) NOT NULL DEFAULT 'error',
status VARCHAR(20) NOT NULL DEFAULT 'open', -- open | resolved | ignored
assignee VARCHAR(200),
title VARCHAR(500),
first_seen_at DATETIME NOT NULL,
last_seen_at DATETIME NOT NULL,
count INT NOT NULL DEFAULT 1,
affected_users INT NOT NULL DEFAULT 0,
platform VARCHAR(30),
release VARCHAR(50),
UNIQUE KEY uk_app_fingerprint (app_key, fingerprint),
INDEX idx_app_key_last_seen (app_key, status, last_seen_at),
INDEX idx_app_key_level (app_key, level, last_seen_at)
);
```
### log_issue_events — Issue 事件明细表
```sql
CREATE TABLE log_issue_events (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
issue_id BIGINT NOT NULL,
event_id VARCHAR(64) NOT NULL, -- 客户端幂等 ID
app_key VARCHAR(64) NOT NULL,
level VARCHAR(20) NOT NULL DEFAULT 'error',
user_id VARCHAR(128),
session_id VARCHAR(128),
exception_type VARCHAR(200),
exception_value TEXT,
stack LONGTEXT,
stack_symbolicated LONGTEXT,
breadcrumbs JSON,
tags JSON,
device JSON,
platform VARCHAR(30),
release VARCHAR(50),
environment VARCHAR(50) DEFAULT 'production',
sdk_name VARCHAR(100),
sdk_version VARCHAR(20),
created_at DATETIME NOT NULL,
UNIQUE KEY uk_event_id (app_key, event_id),
INDEX idx_issue_created (issue_id, created_at),
INDEX idx_app_key (app_key)
);
```
### log_events — 自定义事件表
```sql
CREATE TABLE log_events (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
event_id VARCHAR(64) NOT NULL, -- 客户端幂等 ID
app_key VARCHAR(64) NOT NULL,
name VARCHAR(200) NOT NULL,
user_id VARCHAR(128),
session_id VARCHAR(128),
properties JSON,
platform VARCHAR(30),
release VARCHAR(50),
environment VARCHAR(50) DEFAULT 'production',
device JSON,
sdk_name VARCHAR(100),
sdk_version VARCHAR(20),
created_at DATETIME NOT NULL,
UNIQUE KEY uk_event_id (app_key, event_id),
INDEX idx_app_key_name (app_key, name),
INDEX idx_app_key_user (app_key, user_id),
INDEX idx_app_key_created (app_key, created_at)
);
```
### log_sessions — Session 表
```sql
CREATE TABLE log_sessions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
session_id VARCHAR(64) NOT NULL,
app_key VARCHAR(64) NOT NULL,
status VARCHAR(20) NOT NULL, -- ok | exited | crashed | abnormal
user_id VARCHAR(128),
platform VARCHAR(30),
release VARCHAR(50),
environment VARCHAR(50) DEFAULT 'production',
started_at DATETIME NOT NULL,
duration_ms BIGINT,
errors INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL,
UNIQUE KEY uk_session_id (app_key, session_id),
INDEX idx_app_key_started (app_key, started_at),
INDEX idx_app_key_status (app_key, status, started_at)
);
```
### sourcemaps — 符号文件表
```sql
CREATE TABLE sourcemaps (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
app_key VARCHAR(64) NOT NULL,
platform VARCHAR(30) NOT NULL,
app_version VARCHAR(50) NOT NULL,
bundle_name VARCHAR(100) DEFAULT 'index',
file_path VARCHAR(500) NOT NULL,
uploaded_at DATETIME NOT NULL,
UNIQUE KEY uk_app_version_bundle (app_key, platform, app_version, bundle_name)
);
```
---
## 13. 变更日志
### v1.1.02026-06-17
**New Features**
- 上报认证:明确认证链路,`appKey` 由 SDK 从 `config.xuqm` 自动读取;服务端新增 BugCollect 服务激活校验(`t_feature_service.enabled`),未开通的 appKey 请求返回 `403 / 40301`
- 幂等保障IssueEvent / LogEvent 新增必填 `eventId`UUID,服务端按此去重
- BreadcrumbsIssueEvent 新增 `breadcrumbs` 数组,携带崩溃前最近 50 条操作轨迹
- device 字段扩充:新增 `locale`、`timezone`、`network`、`isEmulator`、`freeMemoryMb`、`buildType`
- Session 上报:新增 `POST /bugcollect/v1/sessions/batch`,支持 crash-free session rate 计算
- 符号化扩展SourceMap 上传新增 `android`ProGuard mapping、`ios`dSYM平台支持
- Issue 管理端点:新增 resolve / ignore / assign / bulk 操作
- Issue 状态:`isResolved: boolean` 替换为 `status: open | resolved | ignored`,新增 `affectedUsers` 字段
- Issue 详情拆分:`GET /issues/{id}` 不再内嵌 Event 列表,新增 `GET /issues/{id}/events` 分页接口
- Issue 趋势:新增 `GET /issues/{id}/trend`
- Webhook 事件扩充:新增 `issue.fatal`、`issue.threshold`、`issue.resolved`;回调体新增签名校验头
- 概览统计:新增 `crashFreeSessionRate`、`openIssues`、`affectedUsers`
- 全文搜索Issue 列表新增 `q` 参数
- 版本筛选Issue 列表新增 `release` 参数
- Environment新增 `integration` 枚举值
- 数据库:新增 `log_sessions` 表;`log_issues` 新增 `status`/`affected_users`/`assignee` 列;`log_issue_events` 新增 `event_id`/`breadcrumbs` 列;补全缺失索引
**Breaking Changes**
- `isResolved` 字段已移除,改用 `status``isResolved: false` → `status: "open"`,`isResolved: true` → `status: "resolved"`
- Issue 详情响应不再包含 `events` 数组,需改用 `GET /issues/{id}/events`
- 每条 Event 现在必须携带 `eventId`;缺失时返回 `400 / 40006`
- BugCollect 服务未激活的 appKey 上报请求返回 `403 / 40301`
**Bug Fixes**
- 修复 Fingerprint 算法中包含 `level` 字段的问题:相同堆栈的 `error``warning` 现在归为同一 Issue
- 修复 Issue 列表查询参数使用废弃的 `type` 字段问题,统一改为 `level`
- 修复 SDK 集成指南中 `BugCollect.setUser()` 错误引用,统一改为 `XuqmSDK.setUserInfo()`
- 修复 Issue 详情示例响应中 `metadata` 字段未同步改名为 `tags` 的问题
### v1.0.02026-06-17
**Breaking Changes**
- 上报端点路径从 `/log/v1/` 统一为 `/bugcollect/v1/`
- Issue 上报请求体从裸 JSON 数组改为信封格式 `{ "sentAt", "sdk", "events" }`
- Issue 事件中 `type` 字段(`android_error` / `native_crash`)替换为 `level` 字段(`fatal` / `error` / `warning` / `info` / `debug`
- Issue 事件中 `message` + `stack` 平铺字段归入 `exception` 对象(`exception.type` / `exception.value` / `exception.stacktrace`
- Issue 事件中 `metadata` 重命名为 `tags`
- Issue 事件中 `appVersion` 重命名为 `release`
- Analytics 事件请求体同步改为信封格式
**New Features**
- Issue 事件新增 `user`(用户信息)、`device`(设备信息)、`sdk`SDK 自标识)字段
- Issue 事件新增 `environment` 字段(`production` / `staging` / `development`
- Analytics 事件新增 `user`、`device`、`sdk`、`environment` 字段
- 信封层新增 `sentAt`(时钟偏差校正)和 `sdk`SDK 版本追踪)
- 查询端点新增 `level` 筛选参数