# routers/tracking.py 接口测试文档 ## 文件功能概述 该文件提供用户行为埋点和 API 路径映射管理相关接口,包括: - 记录用户行为埋点 - 查询用户埋点记录 - 添加 API 路径与名称的映射关系 - 查询所有 API 映射关系 所有接口均需要 Token 认证。 路由前缀:`/apiv1`(以 `routers/__init__.py` 中注册为准) --- ## 接口列表 --- ### 1. POST `/apiv1/tracking/record` — 记录埋点 **功能说明:** 记录用户访问某个 API 的行为。自动获取客户端 IP 和请求方法,生成 UUID 作为请求 ID。 **是否需要认证:** 是(通过 `request.state.user` 获取用户信息) **请求方式:** POST **请求体(JSON,Pydantic 模型 `TrackingRequest`):** | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | api_path | string | 是 | — | 被记录的 API 路径 | | api_name | string | 否 | "" | API 名称/描述 | **业务逻辑:** 1. 从 `request.state.user` 获取 `user_id` 2. 从 `request.client.host` 获取客户端 IP(client 为 None 时为空字符串) 3. 生成 UUID v4 作为 `request_id` 4. 创建 `TrackingRecord` 记录并提交 5. 返回生成的 `request_id` **自动记录的字段:** | 字段 | 来源 | 说明 | |------|------|------| | user_id | request.state.user.user_id | 当前用户 ID | | method | request.method | HTTP 请求方法(此处固定为 POST) | | request_id | uuid.uuid4() | 唯一请求标识 | | ip_address | request.client.host | 客户端 IP 地址 | | created_at | get_unix() | 当前 Unix 时间戳 | **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data.request_id | string | UUID 格式的请求 ID | **测试用例:** #### 用例 1:正常记录埋点 ```json // 请求 POST /apiv1/tracking/record token: <有效Token> Content-Type: application/json { "api_path": "/apiv1/chat/send", "api_name": "发送聊天消息" } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "记录成功", "data": { "request_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` > `request_id` 为 UUID v4 格式字符串,每次不同。 #### 用例 2:不提供 api_name(使用默认空字符串) ```json // 请求 POST /apiv1/tracking/record token: <有效Token> Content-Type: application/json { "api_path": "/apiv1/get_hot_question" } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "记录成功", "data": { "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } } ``` > 数据库中 `api_name` 存储为空字符串。 #### 用例 3:缺少必填字段 api_path ```json // 请求 POST /apiv1/tracking/record token: <有效Token> Content-Type: application/json { "api_name": "测试接口" } // 预期响应 (HTTP 422) { "detail": [ { "loc": ["body", "api_path"], "msg": "field required", "type": "value_error.missing" } ] } ``` #### 用例 4:未认证 ```json // 请求 POST /apiv1/tracking/record Content-Type: application/json { "api_path": "/apiv1/chat/send" } // 预期响应 (HTTP 401) { "statusCode": 401, "msg": "未认证" } ``` #### 用例 5:连续多次记录(每次生成不同 request_id) ```json // 请求(第一次) POST /apiv1/tracking/record token: <有效Token> Content-Type: application/json { "api_path": "/apiv1/chat/send", "api_name": "发送消息" } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "记录成功", "data": { "request_id": "uuid-1" } } // 请求(第二次,相同 api_path) POST /apiv1/tracking/record token: <有效Token> Content-Type: application/json { "api_path": "/apiv1/chat/send", "api_name": "发送消息" } // 预期响应 (HTTP 200) // request_id 与第一次不同 { "statusCode": 200, "msg": "记录成功", "data": { "request_id": "uuid-2" } } ``` > 同一 API 路径可被多次记录,不做去重。 --- ### 2. GET `/apiv1/tracking/records` — 获取埋点记录 **功能说明:** 查询当前用户的埋点记录,按创建时间倒序排列,支持限制返回条数。 **是否需要认证:** 是 **请求方式:** GET **请求参数(Query):** | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | limit | int | 否 | 100 | 返回的最大记录条数 | **业务逻辑:** - 通过 `user.user_id` 过滤当前用户的记录 - 按 `created_at` 倒序排列(最新的在前) - 使用 `limit` 限制返回条数(非分页模式) **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data[].id | int | 记录 ID | | data[].api_path | string | API 路径 | | data[].api_name | string | API 名称 | | data[].created_at | int | 创建时间(Unix 时间戳) | > 注意:响应中不包含 `method`、`request_id`、`ip_address` 等字段。 **测试用例:** #### 用例 1:正常查询(默认 limit=100) ```json // 请求 GET /apiv1/tracking/records token: <有效Token> // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [ { "id": 50, "api_path": "/apiv1/chat/send", "api_name": "发送聊天消息", "created_at": 1700000500 }, { "id": 49, "api_path": "/apiv1/get_hot_question", "api_name": "获取热点问题", "created_at": 1700000400 } ] } ``` #### 用例 2:指定 limit ```json // 请求 GET /apiv1/tracking/records?limit=5 token: <有效Token> // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [...] // 最多 5 条记录 } ``` #### 用例 3:无记录 ```json // 请求 GET /apiv1/tracking/records token: <新用户Token,无埋点记录> // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [] } ``` #### 用例 4:未认证 ```json // 请求 GET /apiv1/tracking/records // 预期响应 (HTTP 401) { "statusCode": 401, "msg": "未认证" } ``` #### 用例 5:只返回当前用户的记录 ```json // 请求 GET /apiv1/tracking/records token: <用户A的Token> // 预期行为:仅返回用户A的埋点记录,不包含其他用户的记录 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [...] // 仅包含 user_id 为用户A的记录 } ``` --- ### 3. POST `/apiv1/tracking/api_mapping` — 添加 API 映射 **功能说明:** 添加一条 API 路径与名称的映射关系。同一 `api_path` 不可重复添加。 **是否需要认证:** 是(代码中显式检查 `if not user`) **请求方式:** POST **请求体(JSON,Pydantic 模型 `ApiMappingRequest`):** | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | api_path | string | 是 | — | API 路径 | | api_name | string | 是 | — | API 名称 | | api_desc | string | 否 | "" | API 描述 | **业务逻辑:** 1. 检查用户认证 2. 查询 `ApiPathMapping` 表中是否已存在相同 `api_path` 3. 若已存在,返回 400 4. 创建新的 `ApiPathMapping` 记录 5. 提交并 refresh 获取自增 ID 6. 返回新记录的 ID **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data.id | int | 新创建的映射记录 ID | **测试用例:** #### 用例 1:正常添加映射 ```json // 请求 POST /apiv1/tracking/api_mapping token: <有效Token> Content-Type: application/json { "api_path": "/apiv1/chat/send", "api_name": "发送聊天消息", "api_desc": "用户发送聊天消息给AI,支持流式响应" } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "添加成功", "data": { "id": 1 } } ``` #### 用例 2:不提供 api_desc(使用默认空字符串) ```json // 请求 POST /apiv1/tracking/api_mapping token: <有效Token> Content-Type: application/json { "api_path": "/apiv1/get_hot_question", "api_name": "获取热点问题" } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "添加成功", "data": { "id": 2 } } ``` #### 用例 3:重复的 api_path ```json // 请求(/apiv1/chat/send 已在用例1中添加) POST /apiv1/tracking/api_mapping token: <有效Token> Content-Type: application/json { "api_path": "/apiv1/chat/send", "api_name": "发送消息(重复)", "api_desc": "重复添加" } // 预期响应 (HTTP 200, 业务码 400) { "statusCode": 400, "msg": "API映射已存在" } ``` #### 用例 4:缺少必填字段 ```json // 请求 POST /apiv1/tracking/api_mapping token: <有效Token> Content-Type: application/json { "api_path": "/apiv1/test" } // 预期响应 (HTTP 422) { "detail": [ { "loc": ["body", "api_name"], "msg": "field required", "type": "value_error.missing" } ] } ``` #### 用例 5:未认证(user 为 None) ```json // 请求 POST /apiv1/tracking/api_mapping Content-Type: application/json { "api_path": "/apiv1/test", "api_name": "测试" } // 预期响应 (HTTP 200, 业务码 401) { "statusCode": 401, "msg": "未授权" } ``` > 注意:此接口返回的未认证消息为"未授权",而非"未认证"。 --- ### 4. GET `/apiv1/tracking/api_mappings` — 获取所有 API 映射 **功能说明:** 查询所有 API 路径映射关系。返回全量数据,无分页、无过滤。 **是否需要认证:** 是(代码中显式检查 `if not user`) **请求方式:** GET **请求参数:** 无 **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data[].id | int | 映射 ID | | data[].api_path | string | API 路径 | | data[].api_name | string | API 名称 | | data[].api_desc | string | API 描述 | | data[].status | int/string | 状态(具体含义取决于模型定义) | **测试用例:** #### 用例 1:正常查询(有数据) ```json // 请求 GET /apiv1/tracking/api_mappings token: <有效Token> // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [ { "id": 1, "api_path": "/apiv1/chat/send", "api_name": "发送聊天消息", "api_desc": "用户发送聊天消息给AI,支持流式响应", "status": 1 }, { "id": 2, "api_path": "/apiv1/get_hot_question", "api_name": "获取热点问题", "api_desc": "", "status": 1 } ] } ``` #### 用例 2:无数据 ```json // 请求 GET /apiv1/tracking/api_mappings token: <有效Token> // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [] } ``` #### 用例 3:未认证(user 为 None) ```json // 请求 GET /apiv1/tracking/api_mappings // 预期响应 (HTTP 200, 业务码 401) { "statusCode": 401, "msg": "未授权" } ``` --- ## 依赖说明 | 依赖项 | 说明 | |--------|------| | `database.get_db` | SQLAlchemy 数据库会话(Depends 注入) | | `database.get_unix` | 获取当前 Unix 时间戳的工具函数 | | `models.tracking.TrackingRecord` | 埋点记录模型(字段:id, user_id, api_path, api_name, method, request_id, ip_address, created_at) | | `models.tracking.ApiPathMapping` | API 路径映射模型(字段:id, api_path, api_name, api_desc, status, created_at) | | `uuid` | Python 标准库,用于生成 UUID v4 | | `request.state.user` | 从中间件注入的用户信息(含 user_id) | ## 代码备注 1. **`record` 接口使用 Pydantic 模型 `TrackingRequest` 接收参数,** 比 `points.py` 中的 `request.json()` 方式更规范,自动处理参数校验。 2. **`record` 接口的 `method` 字段记录的是当前请求的 HTTP 方法(POST),** 而非被埋点的目标 API 的请求方法。如果需要记录目标 API 的方法,需要在请求体中额外传入。 3. **`records` 接口使用 `limit` 而非分页模式,** 无法获取第 100 条之后的数据(需调大 limit)。不返回 total 总数。 4. **`records` 接口响应字段较少,** 不包含 `method`、`request_id`、`ip_address` 等存储在数据库中的字段。 5. **`api_mapping` 和 `api_mappings` 接口使用显式的 `if not user` 检查,** 返回的错误消息为"未授权"(而非其他接口常用的"未认证")。 6. **`api_mapping` 通过 `api_path` 做唯一性检查,** 同一路径不可重复添加映射。但不支持更新或删除已有映射。 7. **`api_mappings` 返回全量数据,** 无分页、无筛选条件。当映射数据量较大时可能存在性能问题。 8. **文件末尾注释说明 `get_user_data_id` 已移至 `routers/total.py`,** 避免路由重复注册。