# routers/total.py 接口测试文档 ## 文件功能概述 该文件提供综合性的通用接口,包括: - 首页展示数据(推荐问题、功能卡片、热点问题) - 政策文件列表查询与下载 - 意见反馈提交 - AI 消息点赞/踩 - 用户数据 ID 查询 大部分接口为公开接口,部分接口需要 Token 认证。 路由前缀:`/apiv1`(以 `routers/__init__.py` 中注册为准) --- ## 接口列表 --- ### 1. GET `/apiv1/recommend_question` — 获取推荐问题 **功能说明:** 从 `RecommendQuestion` 表中查询最多 10 条推荐问题。无分页、无排序、无过滤条件。 **是否需要认证:** 否 **请求方式:** GET **请求参数:** 无 **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data[].id | int | 问题 ID | | data[].question | string | 问题内容 | **测试用例:** #### 用例 1:正常查询(有数据) ```json // 请求 GET /apiv1/recommend_question // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [ {"id": 1, "question": "如何进行隧道安全检查?"}, {"id": 2, "question": "桥梁施工有哪些注意事项?"}, {"id": 3, "question": "安全帽的正确佩戴方式是什么?"} ] } ``` #### 用例 2:无数据 ```json // 请求 GET /apiv1/recommend_question // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [] } ``` #### 用例 3:数据超过 10 条(只返回前 10 条) ```json // 请求 GET /apiv1/recommend_question // 预期行为:数据库中有 15 条记录,仅返回前 10 条 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [...] // 最多 10 条 } ``` --- ### 2. GET `/apiv1/get_policy_file` — 获取政策文件列表 **功能说明:** 分页查询未删除的政策文件列表,可按 `policy_type` 筛选。按更新时间倒序排列。 **是否需要认证:** 否 **请求方式:** GET **请求参数(Query):** | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | policy_type | int | 否 | None | 政策类型筛选(0 或不传表示查全部) | | page | int | 否 | 1 | 页码 | | page_size | int | 否 | 20 | 每页条数 | **业务逻辑:** - `policy_type` 为 `None` 或 `0` 时不做类型筛选 - 按 `updated_at` 倒序排列 **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data.total | int | 总记录数 | | data.items[].id | int | 文件 ID | | data.items[].policy_name | string | 政策文件名称 | | data.items[].policy_file_url | string | 文件 URL | | data.items[].policy_type | int | 政策类型 | | data.items[].file_type | string | 文件类型(如 pdf, docx) | | data.items[].view_count | int | 查看次数 | | data.items[].created_at | int | 创建时间 | **测试用例:** #### 用例 1:默认查询(不筛选类型) ```json // 请求 GET /apiv1/get_policy_file // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": { "total": 25, "items": [ { "id": 10, "policy_name": "2024年安全生产管理规定", "policy_file_url": "https://oss.example.com/policy/safety_2024.pdf", "policy_type": 1, "file_type": "pdf", "view_count": 150, "created_at": 1700000000 } ] } } ``` #### 用例 2:按类型筛选 ```json // 请求 GET /apiv1/get_policy_file?policy_type=2&page=1&page_size=10 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": { "total": 5, "items": [...] } } ``` #### 用例 3:policy_type=0(等同于不筛选) ```json // 请求 GET /apiv1/get_policy_file?policy_type=0 // 预期行为:policy_type=0 时不做类型筛选,查全部 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": { "total": 25, "items": [...] } } ``` #### 用例 4:分页 — 第二页 ```json // 请求 GET /apiv1/get_policy_file?page=2&page_size=5 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": { "total": 25, "items": [...] // 第 6~10 条 } } ``` #### 用例 5:无数据 ```json // 请求 GET /apiv1/get_policy_file?policy_type=99 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": { "total": 0, "items": [] } } ``` --- ### 3. GET `/apiv1/get_function_card` — 获取功能卡片 **功能说明:** 从 `FunctionCard` 表中查询最多 4 张功能卡片,用于首页展示。 **是否需要认证:** 否 **请求方式:** GET **请求参数:** 无 **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data[].id | int | 卡片 ID | | data[].title | string | 功能标题(对应 function_title) | | data[].icon | string | 功能图标(对应 function_icon) | | data[].description | string | 功能描述(对应 function_content) | | data[].business_type | string | 业务类型(对应 function_type) | **测试用例:** #### 用例 1:正常查询 ```json // 请求 GET /apiv1/get_function_card // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [ { "id": 1, "title": "智能问答", "icon": "chat-icon", "description": "基于AI的智能问答系统", "business_type": "chat" }, { "id": 2, "title": "隐患识别", "icon": "hazard-icon", "description": "AI自动识别施工现场隐患", "business_type": "hazard" } ] } ``` #### 用例 2:无数据 ```json // 请求 GET /apiv1/get_function_card // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [] } ``` --- ### 4. GET `/apiv1/get_hot_question` — 获取热点问题 **功能说明:** 查询点击量最高的前 3 个热点问题,按 `click_count` 倒序排列。 **是否需要认证:** 否 **请求方式:** GET **请求参数:** 无 **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data[].id | int | 问题 ID | | data[].question | string | 问题内容 | | data[].click_count | int | 点击次数(null 时返回 0) | **测试用例:** #### 用例 1:正常查询 ```json // 请求 GET /apiv1/get_hot_question // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [ {"id": 5, "question": "高空作业安全规范有哪些?", "click_count": 328}, {"id": 3, "question": "隧道施工中常见的安全隐患?", "click_count": 256}, {"id": 8, "question": "如何正确使用个人防护装备?", "click_count": 189} ] } ``` #### 用例 2:无数据 ```json // 请求 GET /apiv1/get_hot_question // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [] } ``` #### 用例 3:click_count 为 null 的记录 ```json // 请求 GET /apiv1/get_hot_question // 预期行为:click_count 为 null 时返回 0 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": [ {"id": 1, "question": "新增的问题", "click_count": 0} ] } ``` --- ### 5. POST `/apiv1/submit_feedback` — 提交意见反馈 **功能说明:** 提交用户反馈。反馈类型支持中文和英文标识,内部映射为数字 ID。 **是否需要认证:** 是(从 `request.state.user` 获取用户信息,user 为 None 时 user_id 设为 0) **请求方式:** POST **请求体(JSON):** | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | feedback_type | string | 是 | — | 反馈类型。支持值见下表 | | content | string | 是 | — | 反馈内容 | | contact | string | 否 | "" | 联系方式 | **反馈类型映射:** | 输入值 | 映射 ID | 说明 | |--------|---------|------| | "功能建议" / "bug" / "问题反馈" | 1 | 功能/问题类 | | "ui" / "界面优化" | 2 | UI 类 | | "experience" / "体验问题" | 3 | 体验类 | | "other" / "其他" | 4 | 其他(默认) | > 不在映射表中的值默认映射为 4(其他)。 **测试用例:** #### 用例 1:正常提交(中文类型) ```json // 请求 POST /apiv1/submit_feedback token: <有效Token> Content-Type: application/json { "feedback_type": "功能建议", "content": "建议增加批量上传图片的功能", "contact": "13800138000" } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "感谢您的反馈!" } ``` #### 用例 2:正常提交(英文类型) ```json // 请求 POST /apiv1/submit_feedback token: <有效Token> Content-Type: application/json { "feedback_type": "bug", "content": "页面加载异常,报 500 错误" } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "感谢您的反馈!" } ``` #### 用例 3:未知反馈类型(映射为 4-其他) ```json // 请求 POST /apiv1/submit_feedback token: <有效Token> Content-Type: application/json { "feedback_type": "unknown_type", "content": "这是一条测试反馈" } // 预期行为:feedback_type 不在映射表中,映射为 4(其他) // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "感谢您的反馈!" } ``` #### 用例 4:不提供 contact(使用默认空字符串) ```json // 请求 POST /apiv1/submit_feedback token: <有效Token> Content-Type: application/json { "feedback_type": "ui", "content": "首页配色不协调" } // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "感谢您的反馈!" } ``` #### 用例 5:未认证(user 为 None,user_id 设为 0) ```json // 请求 POST /apiv1/submit_feedback Content-Type: application/json { "feedback_type": "other", "content": "匿名反馈" } // 预期行为:user 为 None 时 user_id=0,仍正常创建记录 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "感谢您的反馈!" } ``` > 注意:代码中 `user_id = user.id if user else 0`,所以未认证用户也可提交,但 user_id 为 0。 #### 用例 6:缺少必填字段 ```json // 请求 POST /apiv1/submit_feedback token: <有效Token> Content-Type: application/json { "feedback_type": "bug" } // 预期响应 (HTTP 422) { "detail": [ { "loc": ["body", "content"], "msg": "field required", "type": "value_error.missing" } ] } ``` --- ### 6. POST `/apiv1/like_and_dislike` — AI 消息点赞/踩 **功能说明:** 对指定的 AI 消息进行点赞(like)或踩(dislike)操作。更新 `AIMessage` 表的 `user_feedback` 字段。 > **注意:** 该接口未做用户认证校验。 **是否需要认证:** 否(代码中未做认证校验) **请求方式:** POST **请求体(JSON):** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | ai_message_id | int | 是 | AI 消息 ID | | action | string | 是 | 操作类型:"like" 或 "dislike" | **业务逻辑:** - `action="like"` → `user_feedback=2`(满意/赞) - `action="dislike"` → `user_feedback=3`(不满意/踩) - 其他 action 值也会设为 3(因为条件仅判断是否为 "like") **测试用例:** #### 用例 1:点赞 ```json // 请求 POST /apiv1/like_and_dislike Content-Type: application/json { "ai_message_id": 100, "action": "like" } // 预期响应 (HTTP 200) // 数据库中 AIMessage.id=100 的 user_feedback 更新为 2 { "statusCode": 200, "msg": "success" } ``` #### 用例 2:踩 ```json // 请求 POST /apiv1/like_and_dislike Content-Type: application/json { "ai_message_id": 100, "action": "dislike" } // 预期响应 (HTTP 200) // 数据库中 AIMessage.id=100 的 user_feedback 更新为 3 { "statusCode": 200, "msg": "success" } ``` #### 用例 3:消息不存在 ```json // 请求 POST /apiv1/like_and_dislike Content-Type: application/json { "ai_message_id": 99999, "action": "like" } // 预期响应 (HTTP 200, 业务码 404) { "statusCode": 404, "msg": "消息不存在" } ``` #### 用例 4:非法 action 值(视为 dislike) ```json // 请求 POST /apiv1/like_and_dislike Content-Type: application/json { "ai_message_id": 100, "action": "invalid_action" } // 预期行为:action != "like",user_feedback 设为 3(dislike) // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success" } ``` #### 用例 5:缺少必填字段 ```json // 请求 POST /apiv1/like_and_dislike Content-Type: application/json { "ai_message_id": 100 } // 预期响应 (HTTP 422) { "detail": [ { "loc": ["body", "action"], "msg": "field required", "type": "value_error.missing" } ] } ``` --- ### 7. GET `/apiv1/get_user_data_id` — 获取用户数据 ID **功能说明:** 通过当前 Token 中的 `account` 字段查询 `UserData` 表,返回对应的主键 ID。 **是否需要认证:** 是 **请求方式:** GET **请求参数:** 无 **业务逻辑:** - 从 `request.state.user` 获取用户信息 - 用 `user.account` 作为条件查询 `UserData.accountID` - 找到则返回 `user_data.id`,未找到返回 404 **成功响应字段:** | 字段 | 类型 | 说明 | |------|------|------| | data.user_data_id | int | UserData 表主键 ID | **测试用例:** #### 用例 1:正常查询 ```json // 请求 GET /apiv1/get_user_data_id token: <有效Token> // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success", "data": { "user_data_id": 42 } } ``` #### 用例 2:用户数据不存在(UserData 表中无匹配 accountID) ```json // 请求 GET /apiv1/get_user_data_id token: <有效Token,但 UserData 表中无对应记录> // 预期响应 (HTTP 200, 业务码 404) { "statusCode": 404, "msg": "用户数据不存在" } ``` #### 用例 3:未认证(user 为 None) ```json // 请求 GET /apiv1/get_user_data_id // 预期响应 (HTTP 200, 业务码 401) { "statusCode": 401, "msg": "未认证" } ``` --- ### 8. POST `/apiv1/policy_file_count` — 更新政策文件查看计数 **功能说明:** 将指定政策文件的 `view_count` 加 1。每次调用自增一次。 > **注意:** 该接口未做用户认证校验,可被无限制调用增加计数。 **是否需要认证:** 否(代码中未做认证校验) **请求方式:** POST **请求体(JSON):** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | policy_file_id | int | 是 | 政策文件 ID | **业务逻辑:** - 查找对应的 `PolicyFile` 记录 - `view_count = (view_count or 0) + 1`(null 安全处理) - 更新 `updated_at` 为当前时间戳 **测试用例:** #### 用例 1:正常计数 ```json // 请求 POST /apiv1/policy_file_count Content-Type: application/json { "policy_file_id": 5 } // 预期响应 (HTTP 200) // 数据库中 PolicyFile.id=5 的 view_count 加 1 { "statusCode": 200, "msg": "success" } ``` #### 用例 2:重复调用(连续计数) ```json // 请求(连续调用两次) POST /apiv1/policy_file_count Content-Type: application/json { "policy_file_id": 5 } // 预期行为:每次调用 view_count +1 // 第一次调用后 view_count=151,第二次调用后 view_count=152 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success" } ``` #### 用例 3:文件不存在 ```json // 请求 POST /apiv1/policy_file_count Content-Type: application/json { "policy_file_id": 99999 } // 预期响应 (HTTP 200, 业务码 404) { "statusCode": 404, "msg": "文件不存在" } ``` #### 用例 4:view_count 为 null 的文件(首次查看) ```json // 请求(针对 view_count 为 null 的文件) POST /apiv1/policy_file_count Content-Type: application/json { "policy_file_id": 8 } // 预期行为:(null or 0) + 1 = 1 // 预期响应 (HTTP 200) { "statusCode": 200, "msg": "success" } ``` #### 用例 5:缺少必填字段 ```json // 请求 POST /apiv1/policy_file_count Content-Type: application/json {} // 预期响应 (HTTP 422) { "detail": [ { "loc": ["body", "policy_file_id"], "msg": "field required", "type": "value_error.missing" } ] } ``` --- ### 9. GET `/apiv1/download_file` — 流式代理下载 OSS 文件 **功能说明:** 接收加密的 OSS 代理 URL,解密后通过 HTTP 流式代理下载文件内容。返回 `StreamingResponse`,而非 JSON。 **是否需要认证:** 否 **请求方式:** GET **请求参数(Query):** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | pdf_oss_download_link | string | 是 | 加密的 OSS 文件代理 URL | **业务逻辑:** 1. 调用 `decrypt_url()` 解密 URL 获取真实 OSS 地址 2. 使用 `httpx.AsyncClient` 流式请求真实 URL 3. 若远程响应非 200,返回错误 JSON 4. 正常时返回 `StreamingResponse`(透传 Content-Type 和 Content-Disposition) **测试用例:** #### 用例 1:正常下载 PDF 文件 ``` // 请求 GET /apiv1/download_file?pdf_oss_download_link=encrypted_url_string // 预期响应 (HTTP 200) // Content-Type: application/pdf // Content-Disposition: attachment; filename="safety_guide.pdf" // Body: [PDF 文件二进制流] ``` #### 用例 2:正常下载其他类型文件 ``` // 请求 GET /apiv1/download_file?pdf_oss_download_link=encrypted_docx_url // 预期响应 (HTTP 200) // Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document // Body: [DOCX 文件二进制流] ``` #### 用例 3:URL 解密失败 ```json // 请求 GET /apiv1/download_file?pdf_oss_download_link=invalid_encrypted_string // 预期响应 (HTTP 200, 业务码 500) { "statusCode": 500, "msg": "下载失败: <解密错误信息>" } ``` #### 用例 4:远程文件不存在(OSS 返回 404) ```json // 请求 GET /apiv1/download_file?pdf_oss_download_link=encrypted_url_of_deleted_file // 预期响应 (HTTP 200, 业务码 404) { "statusCode": 404, "msg": "文件下载失败" } ``` #### 用例 5:远程服务器异常(OSS 返回 500) ```json // 请求 GET /apiv1/download_file?pdf_oss_download_link=encrypted_url // 预期响应 (HTTP 200, 业务码 500) { "statusCode": 500, "msg": "文件下载失败" } ``` #### 用例 6:网络连接超时 ```json // 请求 GET /apiv1/download_file?pdf_oss_download_link=encrypted_url_with_unreachable_host // 预期响应 (HTTP 200, 业务码 500) { "statusCode": 500, "msg": "下载失败: " } ``` #### 用例 7:缺少参数 ```json // 请求 GET /apiv1/download_file // 预期响应 (HTTP 422) { "detail": [ { "loc": ["query", "pdf_oss_download_link"], "msg": "field required", "type": "value_error.missing" } ] } ``` --- ## 依赖说明 | 依赖项 | 说明 | |--------|------| | `database.get_db` | SQLAlchemy 数据库会话(Depends 注入) | | `models.total.RecommendQuestion` | 推荐问题模型(字段:id, question) | | `models.total.PolicyFile` | 政策文件模型(字段:id, policy_name, policy_file_url, policy_type, file_type, view_count, is_deleted, created_at, updated_at) | | `models.total.FunctionCard` | 功能卡片模型(字段:id, function_title, function_icon, function_content, function_type) | | `models.total.HotQuestion` | 热点问题模型(字段:id, question, click_count) | | `models.total.FeedbackQuestion` | 反馈问题模型(字段:id, feedback_type, feedback_content, feedback_user_phone, user_id, created_at, updated_at) | | `models.chat.AIMessage` | AI 消息模型(字段:id, user_feedback, updated_at) | | `models.user_data.UserData` | 用户数据模型(字段:id, accountID) | | `utils.crypto.decrypt_url` | URL 解密工具函数 | | `httpx` | 异步 HTTP 客户端,用于流式代理下载 | | `request.state.user` | 从中间件注入的用户信息(含 id, account) | ## 代码备注 1. **`recommend_question` 无排序条件,** 返回数据库中前 10 条记录,顺序取决于数据库默认排序。 2. **`get_policy_file` 的 `policy_type=0` 等同于不筛选,** 与 `None` 行为一致。 3. **`get_function_card` 的响应字段名与模型字段名不一致:** `title→function_title`, `icon→function_icon`, `description→function_content`, `business_type→function_type`。 4. **`get_hot_question` 对 `click_count` 做了 null 安全处理:** `q.click_count or 0`。 5. **`submit_feedback` 的认证是软性的:** 未认证时 `user_id=0`,仍可成功创建反馈记录。 6. **`like_and_dislike` 未做用户认证校验,** 任何人可对任何消息点赞/踩,且无法取消(只能覆盖为新状态)。 7. **`like_and_dislike` 的 action 判断不严格:** 非 "like" 的任何值都被视为 "dislike"(`user_feedback=3`)。 8. **`policy_file_count` 未做认证和防重复校验,** 可被无限调用导致计数虚高。 9. **`download_file` 使用流式代理模式,** 服务端作为中间代理转发 OSS 文件内容,不直接暴露 OSS 真实 URL 给前端。 10. **`download_file` 参数名为 `pdf_oss_download_link`,** 但实际支持任意文件类型下载,不限于 PDF。