test_total.md 21 KB

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:正常查询(有数据)

// 请求
GET /apiv1/recommend_question

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": [
    {"id": 1, "question": "如何进行隧道安全检查?"},
    {"id": 2, "question": "桥梁施工有哪些注意事项?"},
    {"id": 3, "question": "安全帽的正确佩戴方式是什么?"}
  ]
}

用例 2:无数据

// 请求
GET /apiv1/recommend_question

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": []
}

用例 3:数据超过 10 条(只返回前 10 条)

// 请求
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_typeNone0 时不做类型筛选
  • 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:默认查询(不筛选类型)

// 请求
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:按类型筛选

// 请求
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(等同于不筛选)

// 请求
GET /apiv1/get_policy_file?policy_type=0

// 预期行为:policy_type=0 时不做类型筛选,查全部
// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "total": 25,
    "items": [...]
  }
}

用例 4:分页 — 第二页

// 请求
GET /apiv1/get_policy_file?page=2&page_size=5

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "total": 25,
    "items": [...]  // 第 6~10 条
  }
}

用例 5:无数据

// 请求
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:正常查询

// 请求
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:无数据

// 请求
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:正常查询

// 请求
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:无数据

// 请求
GET /apiv1/get_hot_question

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": []
}

用例 3:click_count 为 null 的记录

// 请求
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:正常提交(中文类型)

// 请求
POST /apiv1/submit_feedback
token: <有效Token>
Content-Type: application/json

{
  "feedback_type": "功能建议",
  "content": "建议增加批量上传图片的功能",
  "contact": "13800138000"
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "感谢您的反馈!"
}

用例 2:正常提交(英文类型)

// 请求
POST /apiv1/submit_feedback
token: <有效Token>
Content-Type: application/json

{
  "feedback_type": "bug",
  "content": "页面加载异常,报 500 错误"
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "感谢您的反馈!"
}

用例 3:未知反馈类型(映射为 4-其他)

// 请求
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(使用默认空字符串)

// 请求
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)

// 请求
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:缺少必填字段

// 请求
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:点赞

// 请求
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:踩

// 请求
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:消息不存在

// 请求
POST /apiv1/like_and_dislike
Content-Type: application/json

{
  "ai_message_id": 99999,
  "action": "like"
}

// 预期响应 (HTTP 200, 业务码 404)
{
  "statusCode": 404,
  "msg": "消息不存在"
}

用例 4:非法 action 值(视为 dislike)

// 请求
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:缺少必填字段

// 请求
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:正常查询

// 请求
GET /apiv1/get_user_data_id
token: <有效Token>

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "success",
  "data": {
    "user_data_id": 42
  }
}

用例 2:用户数据不存在(UserData 表中无匹配 accountID)

// 请求
GET /apiv1/get_user_data_id
token: <有效Token,但 UserData 表中无对应记录>

// 预期响应 (HTTP 200, 业务码 404)
{
  "statusCode": 404,
  "msg": "用户数据不存在"
}

用例 3:未认证(user 为 None)

// 请求
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:正常计数

// 请求
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:重复调用(连续计数)

// 请求(连续调用两次)
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:文件不存在

// 请求
POST /apiv1/policy_file_count
Content-Type: application/json

{
  "policy_file_id": 99999
}

// 预期响应 (HTTP 200, 业务码 404)
{
  "statusCode": 404,
  "msg": "文件不存在"
}

用例 4:view_count 为 null 的文件(首次查看)

// 请求(针对 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:缺少必填字段

// 请求
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 解密失败

// 请求
GET /apiv1/download_file?pdf_oss_download_link=invalid_encrypted_string

// 预期响应 (HTTP 200, 业务码 500)
{
  "statusCode": 500,
  "msg": "下载失败: <解密错误信息>"
}

用例 4:远程文件不存在(OSS 返回 404)

// 请求
GET /apiv1/download_file?pdf_oss_download_link=encrypted_url_of_deleted_file

// 预期响应 (HTTP 200, 业务码 404)
{
  "statusCode": 404,
  "msg": "文件下载失败"
}

用例 5:远程服务器异常(OSS 返回 500)

// 请求
GET /apiv1/download_file?pdf_oss_download_link=encrypted_url

// 预期响应 (HTTP 200, 业务码 500)
{
  "statusCode": 500,
  "msg": "文件下载失败"
}

用例 6:网络连接超时

// 请求
GET /apiv1/download_file?pdf_oss_download_link=encrypted_url_with_unreachable_host

// 预期响应 (HTTP 200, 业务码 500)
{
  "statusCode": 500,
  "msg": "下载失败: <httpx 超时或连接错误信息>"
}

用例 7:缺少参数

// 请求
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_filepolicy_type=0 等同于不筛选,None 行为一致。
  3. get_function_card 的响应字段名与模型字段名不一致: title→function_title, icon→function_icon, description→function_content, business_type→function_type
  4. get_hot_questionclick_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。