test_hazard.md 10 KB

routers/hazard.py 接口测试文档

文件功能概述

该文件提供隐患识别相关的接口,包括:

  • 基于 YOLO 的图片隐患识别(下载图片 → AI 检测 → 绘制标注框和水印 → 上传结果)
  • 保存识别步骤进度

所有接口均需要 Token 认证。

路由前缀:/apiv1(以 routers/__init__.py 中注册为准)


接口列表


1. POST /apiv1/hazard — 隐患识别

功能说明: 接收一张图片的 OSS URL,执行以下完整流程:

  1. 解密 OSS 代理 URL(失败则直接使用原 URL)
  2. 通过 HTTP 下载图片到内存
  3. 调用 YOLO 服务进行隐患检测
  4. 在图片上绘制检测边界框(红色矩形 + 标签 + 置信度)
  5. 添加 45 度角平铺水印(用户名、账号、日期)
  6. 将标注后的结果图片上传至 OSS
  7. 在数据库中插入 RecognitionRecord 记录
  8. 返回识别结果(隐患数量、详情、结果图片 URL)

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | image_url | string | 是 | — | 图片的 OSS URL(可能是加密的代理 URL) | | scene_type | string | 否 | "" | 场景类型(如 "隧道施工"、"桥梁施工") | | user_name | string | 否 | "" | 用户名(用于水印,不传则使用 user.account) | | user_account | string | 否 | "" | 用户账号(用于请求体传递,实际水印使用 user.account) |

成功响应字段: | 字段 | 类型 | 说明 | |------|------|------| | data.record_id | int | 识别记录 ID | | data.hazard_count | int | 检测到的隐患数量 | | data.hazards | array | 隐患详情列表,每项含 bbox, label, confidence | | data.result_image_url | string | 标注后的结果图片 OSS URL | | data.original_image_url | string | 原始图片 URL(与请求中的一致) |

测试用例:

用例 1:正常识别(有隐患)

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

{
  "image_url": "encrypted_oss_url_of_construction_site_image",
  "scene_type": "隧道施工",
  "user_name": "张三",
  "user_account": "zhangsan"
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "识别成功",
  "data": {
    "record_id": 1,
    "hazard_count": 3,
    "hazards": [
      {
        "bbox": [100, 150, 300, 400],
        "label": "未佩戴安全帽",
        "confidence": 0.92
      },
      {
        "bbox": [500, 200, 650, 350],
        "label": "临边防护缺失",
        "confidence": 0.85
      },
      {
        "bbox": [200, 400, 400, 550],
        "label": "材料堆放不规范",
        "confidence": 0.78
      }
    ],
    "result_image_url": "https://oss.example.com/hazard_detection/123/1700000000.jpg",
    "original_image_url": "encrypted_oss_url_of_construction_site_image"
  }
}

用例 2:正常识别(无隐患)

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

{
  "image_url": "encrypted_oss_url_of_safe_image",
  "scene_type": "桥梁施工"
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "识别成功",
  "data": {
    "record_id": 2,
    "hazard_count": 0,
    "hazards": [],
    "result_image_url": "https://oss.example.com/hazard_detection/123/1700000001.jpg",
    "original_image_url": "encrypted_oss_url_of_safe_image"
  }
}

用例 3:图片 URL 无效(下载失败)

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

{
  "image_url": "https://invalid-url.example.com/nonexistent.jpg",
  "scene_type": "隧道施工"
}

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

用例 4:YOLO 服务异常

// 请求(YOLO 服务不可用时)
POST /apiv1/hazard
token: <有效Token>
Content-Type: application/json

{
  "image_url": "valid_encrypted_oss_url",
  "scene_type": "隧道施工"
}

// 预期响应 (HTTP 200, 业务码 500)
{
  "statusCode": 500,
  "msg": "处理失败: <YOLO 服务错误信息>"
}

用例 5:未认证

// 请求
POST /apiv1/hazard

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

用例 6:缺少必填字段 image_url

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

{
  "scene_type": "隧道施工"
}

// 预期响应 (HTTP 422)
{
  "detail": [
    {
      "loc": ["body", "image_url"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

用例 7:不传 user_name(使用 user.account 作为水印)

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

{
  "image_url": "valid_encrypted_oss_url",
  "scene_type": "隧道施工"
}

// 预期响应 (HTTP 200)
// 水印使用 token 中用户的 account 字段
{
  "statusCode": 200,
  "msg": "识别成功",
  "data": {
    "record_id": 3,
    "hazard_count": 1,
    "hazards": [...],
    "result_image_url": "https://oss.example.com/hazard_detection/123/1700000002.jpg",
    "original_image_url": "valid_encrypted_oss_url"
  }
}

2. POST /apiv1/save_step — 保存识别步骤

功能说明: 更新指定隐患识别记录的当前步骤编号。用于前端多步骤流程中保存用户当前进度。

是否需要认证:

请求方式: POST

请求体(JSON): | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | record_id | int | 是 | 识别记录 ID | | current_step | int | 是 | 当前步骤编号 |

业务逻辑:

  • 通过 record_id + user_id(当前登录用户)双重条件查找 RecognitionRecord
  • 更新 current_stepupdated_at 字段
  • 若未找到匹配记录(记录不存在或不属于当前用户),返回 404
  • 异常时执行数据库回滚

测试用例:

用例 1:正常保存步骤

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

{
  "record_id": 1,
  "current_step": 2
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "保存成功",
  "data": {
    "record_id": 1,
    "current_step": 2
  }
}

用例 2:更新到最后一步

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

{
  "record_id": 1,
  "current_step": 5
}

// 预期响应 (HTTP 200)
{
  "statusCode": 200,
  "msg": "保存成功",
  "data": {
    "record_id": 1,
    "current_step": 5
  }
}

用例 3:记录不存在

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

{
  "record_id": 99999,
  "current_step": 2
}

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

用例 4:记录属于其他用户(权限隔离)

// 请求(用用户 A 的 Token 修改用户 B 的记录)
POST /apiv1/save_step
token: <用户A的Token>
Content-Type: application/json

{
  "record_id": 5,
  "current_step": 3
}

// 预期响应 (HTTP 200, 业务码 404)
// 因为 user_id 不匹配,查询结果为空
{
  "statusCode": 404,
  "msg": "记录不存在"
}

用例 5:未认证

// 请求
POST /apiv1/save_step

// 预期响应 (HTTP 401)
{
  "statusCode": 401,
  "msg": "未提供认证Token"
}

用例 6:缺少必填字段

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

{
  "record_id": 1
}

// 预期响应 (HTTP 422)
{
  "detail": [
    {
      "loc": ["body", "current_step"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}

用例 7:数据库异常

// 预期响应 (HTTP 200, 业务码 500)
{
  "statusCode": 500,
  "msg": "保存失败: <具体错误信息>"
}

注意:此时代码会执行 db.rollback() 回滚事务。


内部函数说明

_draw_boxes_and_watermark(image_bytes, hazards, user_name, user_account) — 绘制标注和水印

功能: 在图片上绘制 YOLO 检测的边界框和 45 度角平铺水印。

参数: | 参数 | 类型 | 说明 | |------|------|------| | image_bytes | bytes | 原始图片二进制数据 | | hazards | list | YOLO 检测结果列表,每项含 bbox(4 元素列表)、labelconfidence | | user_name | string | 用户名(水印文本之一) | | user_account | string | 用户账号(水印文本之一) |

返回: bytes — 处理后的 JPEG 图片二进制数据

处理逻辑:

  1. 用 PIL 打开图片,转为 RGBA 模式
  2. 创建透明覆盖层,为每个隐患绘制红色矩形框和标签文字
  3. 创建水印层,以 45 度角平铺三行水印文本(用户名、账号、日期)
  4. 旋转水印层后与原图合成
  5. 转为 RGB 保存为 JPEG(quality=95)
  6. 如处理失败,返回原始图片字节

该函数为内部函数,不直接暴露为 API 接口。


依赖说明

依赖项 说明
database.get_db SQLAlchemy 数据库会话
models.scene.RecognitionRecord 识别记录模型(字段:id, user_id, scene_type, original_image_url, recognition_image_url, hazard_count, hazard_details, current_step, created_at, updated_at, is_deleted)
services.yolo_service YOLO 目标检测服务,提供 detect_hazards(url, scene_type) 方法
services.oss_service OSS 存储服务,提供 upload_bytes(data, filename) 方法
utils.crypto.decrypt_url URL 解密工具函数
PIL (Pillow) 图片处理库
httpx 异步 HTTP 客户端,用于下载图片
request.state.user 从中间件注入的用户信息(含 user_id, account 等)

代码备注

  1. hazard 接口的 URL 解密有容错处理:decrypt_url 失败时直接使用原始 URL,不会抛出异常。
  2. save_step 接口通过 record_id + user_id 双重过滤实现了用户间的数据隔离——用户只能修改自己的记录。
  3. 水印字体加载有三级回退:Linux DejaVu → Windows 微软雅黑 → PIL 默认字体。
  4. 图片处理失败时(_draw_boxes_and_watermark 异常),返回原始未标注图片,不会导致整个接口失败。
  5. hazard_details 字段以 JSON 字符串形式存储在数据库中(json.dumpsensure_ascii=False)。
  6. 结果图片以 JPEG 格式保存,quality=95,存储路径为 hazard_detection/{user_id}/{timestamp}.jpg