# PingCAP University Online-Exam API 文档

- [API说明](#API-说明)
- [名词解释](#名词解释)
- [获取服务端时间](#获取服务端时间)
- [获取考试报名详情](#获取考试报名详情)
- [报名考试](#报名考试)
- [获取我的在线考试列表](#获取我的在线考试列表)
- [获取我的在线考试详情](#获取我的在线考试详情)
- [获取我的考试报名详情](#获取我的考试报名详情)
- [修改我的考试报名信息](#修改我的考试报名信息)
- [开始考试 - 人脸识别](#开始考试---人脸识别)
- [开始考试 - 环境检查](#开始考试---环境检查)
- [开始考试 - 获取试题及草稿](#开始考试---获取试题及草稿)
- [考试中 - 保存草稿](#考试中---保存草稿)
- [考试中 - WebSocket 通信](#考试中---WebSocket-通信)
- [考试中 - 考生身份信息修改](#考试中---考生身份信息修改)
- [考试中 - 交卷](#考试中---交卷)
- [考试中 - 行为上报](#考试中---行为上报)
- [考试中 - 考生违章抓拍](#考试中---考生违章抓拍)
- [测试 - 考试状态重置](#测试---考试状态重置)
- [测试 - 设置报名阶段考试状态](#测试---设置报名阶段考试状态)

### API 说明

- 所有的 API 调用都有频率限制，未认证用户 60 次/分钟, 认证用户 100 次/分钟
- i18n支持(zh-hans/en-us), 在请求 Header 中声明： Accept-Language: zh-hans
- 当 cookie 中有 csrftoken 时, put|post|delete|patch 等请求需在 header 中声明: X-CSRFTOKEN
- 权限说明
    - Anyone
    - IsAuthenticated
- 以下 API 文档中均未提及 5xx 错误, 但你在使用 API 的时候应考虑到相关错误(包括网络延时)
- 开发模式的登录方式: 登录 dev-accounts.pingcap.com 后, 将该域名的 `dev_sid` cookie 拷贝到本地, 再刷新页面即可
- 线上登录方式: 需要登录时重定向到该链接即可 http://accounts.pingcap.com/login?redirect_to=https%3A//university.pingcap.com/{!!my_path!!}


### 名词解释

- 课程设计
```
                                                      | -> PPT / PDF Lesson
Course(课程方向) -> Chapter(章节) -> Lesson(具体课程) -- | -> Video Lesson
                                                      | —> Offline
                                                      | -> complex Lesson (复杂课程, 上面 3 种的任意组合)
```

- 关于 URL slug: https://prettylinks.com/2018/03/url-slugs/



### 获取服务端时间

### Request

- Method: **GET**
- URL: `/api/server-time`
- Content-Type: application/json
- Permission: AllowAny

#### Response

- Success
  - HTTP 200

    ```json
    {
        "detail": "success",
        "data": {
            "now": "2020-09-21 14:06:36",  // 北京时间
            "now_timestamp": 1600668396,
        }
    }
    ```



### 获取考试报名详情

#### Request

- Method: **GET**
- URL: `/api/exams/<exam_flag>/enroll`, exam_flag 可选 `PCTA` 和 `PCTP`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)


#### Response Example

- Success
  - HTTP 200

    ```json
    {
      "detail": "success",
      "data": {
        "available_sessions": [
          {    // 可用的考试场次
            "display": "2020年11月14日 15:00 ~ 16:00",   // 对用户显示
            "start_at": "2020-11-14 15:00:00+0800",     // 考试开始时间
            "end_at": "2020-11-14 16:00:00+0800",       // 考试结束时间
            "exam_id": 1,                               // 考试 ID，向服务端提交数据时用
          }, {
            "display": "2020年11月15日 15:00 ~ 16:00",   // 对用户显示
            "start_at": "2020-11-15 15:00:00+0800",     // 考试开始时间
            "end_at": "2020-11-15 16:00:00+0800",       // 考试结束时间
            "exam_id": 2,                               // 考试 ID，向服务端提交数据时用
          }
        ]
      }
    }
    ```

- Failed

   - HTTP 401

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```



### 报名考试

#### Request

- Method: **POST**
- URL: `/api/exams/<exam_flag>/enroll`, exam_flag 可选 `PCTA` 和 `PCTP`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- Body:

  ```json
    {
      "name": "张三",  // 姓名
      "chinese_id": "610xxxxxxxxxxxxx",  // 身份证号码
      "phone": "136110xxxx",  // 手机号
      "email": "test@example.com", // 邮箱
      "company": "PingCAP", // 公司
      "city": "北京",  // 所在城市
      "tug_role": "在 TUG 中的角色",
      "exam_id": 1,  // 报名的考试 ID
      "payment_method": 0,  // 付费方式, 0 -> 优惠码,  1 -> 在线支付
      "promotion_code": "1231312",  // 优惠码, 付费方式为优惠码时必填
      "online_payment_success": true, // 是否完成在线支付
      "need_receipt": true, // 是否需要发票
      "receipt_header": "北京平凯星辰科技有限公司", // 发票抬头
      "receipt_tin": "1u2y3981h31u2b31", // 纳税人识别号
      "obtained_dba_certs": "Oracle 认证,微软 MCDBA 认证",  // 已经取得的 DBA 相关认证, 多个选项使用英文逗号隔开
    }
    ```


#### Response Example

- Success
  - HTTP 200

    ```json
    {
      "detail": "success"
    }
    ```

- Failed

   - HTTP 401

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```

  - HTTP 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "name": ["不能为空"],
        "chinese_id": ["身份证号码格式错误"],
        "phone": ["手机号码不合法"],
        "exam_id": ["所选考试报名已结束", "人数已满，请选择其他场次"]
      }
    }
    ```



### 获取我的在线考试列表

#### Request

- Method: **GET**
- URL: `/api/my-onlineexam`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)


#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success",
        "data": [
            {
                "name": "PCTA",  // 考试名称
                "slug": "PCTA-2020-09-01",  // 考试 Slug, 用法: /api/my-onlineexam/PCTA-2020-09-01
                "short_description": "在线笔试, 通过后获得 PCTA 证书",  // 考试说明
                "rules": "考试规则",
                "start_at": "2020-09-01 11:00:00", // 开始时间
                "end_at": "2020-09-01 12:00:00", // 结束时间
                "status": 0, // 考试状态: 0 -> 已报名,  1 -> 待参加考试,  2 -> 考试中,  3 -> 考试结束,  4 -> 未参加考试
                "status_display": "已报名",  // 考试状态的文字描述
                "number_of_questions": 60,  // 试题数量
                "total_score": 60, // 总分
                "passing_score": 30, // 及格线
                "score": 54, // 考试分数, 仅考试状态为 3/4 时该值可能存在，否则为 null
                "passed": false, // 是否通过, 仅考试状态为 3/4 时该值可能存在，否则为 null
                "cover_url": "/uploads/58/233/bae208e11eaa65cacde48001122.png",  // 考试封面
                "cert_url": "/_uploads/58/cert.png", // 考试证书链接, 如果没有则为 null
            },
            {
                "name": "PCTP",  // 考试名称
                "slug": "PCTP-2020-09-01",  // 考试 Slug
                "short_description": "在线笔试, 通过后获得 PCTP 机试资格",  // 考试说明
                "rules": "考试规则",
                "start_at": "2020-09-01 13:00:00", // 开始时间
                "end_at": "2020-09-01 14:00:00", // 结束时间
                "status": 0, // 考试状态: 0 -> 已报名,  1 -> 待参加考试,  2 -> 考试中,  3 -> 考试结束,  4 -> 未参加考试
                "status_display": "已报名",  // 考试状态的文字描述
                "number_of_questions": 60,  // 试题数量
                "total_score": 60, // 总分
                "passing_score": 30, // 及格线
                "score": 54, // 考试分数, 仅考试状态为 3/4 时该值可能存在，否则为 null
                "passed": false, // 是否通过, 仅考试状态为 3/4 时该值可能存在，否则为 null
                "cover_url": "/uploads/58/233/bae208e11eaa65cacde48001122.png",  // 考试封面
                "cert_url": null, // 考试证书链接, 如果没有则为 null
            }
        ]
    }
    ```

- Failed

   - HTTP 401

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```

  - HTTP 429

    ```json
    { "detail": "rate limit" }
    ```



### 获取我的在线考试详情

#### Request

- Method: **GET**
- URL: `/api/my-onlineexam/{slug}`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)

#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success",
        "data": {
            "name": "PCTA",  // 考试名称
            "slug": "PCTA-2020-09-01",  // 考试 Slug, 用法: /api/my-onlineexam/PCTA-2020-09-01
            "short_description": "在线笔试, 通过后获得 PCTA 证书",  // 考试说明
            "rules": "考试规则",   // 考试规则，Markdown 格式
            "start_at": "2020-09-01 11:00:00", // 开始时间
            "end_at": "2020-09-01 12:00:00", // 结束时间
            "status": 0, // 考试状态: 0 -> 已报名,  1 -> 待参加考试,  2 -> 考试中,  3 -> 考试结束,  4 -> 未参加考试
            "status_display": "已报名",  // 考试状态的文字描述
            "number_of_questions": 60,  // 试题数量
            "total_score": 60, // 总分
            "passing_score": 30, // 及格线
            "score": 54, // 考试分数, 仅考试状态为 3/4 时该值可能存在，否则为 null
            "passed": false, // 是否通过, 仅考试状态为 3/4 时该值可能存在，否则为 null
            "is_force_submitted": false, // 是否强制交卷, 如果考试结束，且该字段为 ture, 则需要在界面展示
            "force_submit_reason": "切换页面行为达到上限，强制交卷", // 强制交卷原因
            "cover_url": "/uploads/58/233/bae208e11eaa65cacde48001122.png",  // 考试封面
            "cert_url": "/_uploads/58/cert.png", // 考试证书链接, 如果没有则为 null
        }
    }
    ```

- Failed

   - HTTP 401

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```

  - HTTP 404

    ```json
    { "detail": "Not Found." }
    ```

  - HTTP 429

    ```json
    { "detail": "rate limit" }
    ```



### 获取我的考试报名详情

#### Request

- Method: **GET**
- URL: `/api/my-onlineexam/{slug}/registration-info`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)

#### Response Example

- Success
  - HTTP 200

    ```json
    {
      "detail": "success",
      "data": {
        "name": "张三",   // 姓名
        "chinese_id": "610428xxxxxxxxx",   // 身份证
        "phone": "136xxxxxx",   // 手机
        "email": "exmaple@mail.com",   // 邮箱
        "company": "公司", // 公司
        "city": "城市",    // 城市
        "tug_role": "TUG",  // 在 TUG 中的角色
        "obtained_dba_certs": "DBA证书1,DBA证书2",   // 获取过的 DBA 认证
        "exam_modification_remaining": 2,  // 考试场次的剩余修改次数
        "can_update_exam": true,   // 当前是否允许修改考试场次
        "available_sessions": [   // 可用的考试场次
          {
            "display": "2020年11月14日 15:00 ~ 16:00",   // 对用户显示
            "start_at": "2020-11-14 15:00:00+0800",     // 考试开始时间
            "end_at": "2020-11-14 16:00:00+0800",       // 考试结束时间
            "exam_id": 1,                               // 考试 ID，向服务端提交数据时用
          }, {
            "display": "2020年11月15日 15:00 ~ 16:00",   // 对用户显示
            "start_at": "2020-11-15 15:00:00+0800",     // 考试开始时间
            "end_at": "2020-11-15 16:00:00+0800",       // 考试结束时间
            "exam_id": 2,                               // 考试 ID，向服务端提交数据时用
          }
        ],
        "exam": {
          "id": 3,  // 当前考试 ID
          "slug": "PCTP-01",  // 当前考试 Slug
          "flag": "PCTA", // 当前考试类型，PCTA or PCTP
          "time_display": "2020年11月15日 11:16 ~ 16:00",  // 考试时间
        }
      }
    }
    ```

- Failed

   - HTTP 401

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```

  - HTTP 404

    ```json
    { "detail": "Not Found." }
    ```

  - HTTP 429

    ```json
    { "detail": "rate limit" }
    ```



### 修改我的考试报名信息

#### Request

- Method: **POST**
- URL: `/api/my-onlineexam/{slug}/registration-info`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- Body:

   ```json
   {
      "name": "姓名",
      "chinese_id": "610429xxxxxxxxx",
      "phone": "136xxxxxx",
      "email": "example@maii.com",
      "company": "PingCAP",
      "city": "北京",
      "tug_role": "角色",
      "obtained_dba_certs": "DBA证书1,DBA证书2",
      "exam_id": 1,   // 考试场次 ID
   }
   ```

#### Response Example

- Success
  - HTTP 200

    ```json
    {
      "detail": "success",
      "data": {
        "exam": {
          "id": 1,
          "slug": "PCTP-01"
        }
      }
    }
    ```

- Failed

   - HTTP 400

    ```json
    {
      "detail": "params wrong",
      "errors": {
        "__all__": ["考试开始后不允许修改报名信息"],
        "name": ["不能为空"],
        "chinese_id": ["身份证号码格式错误"],
        "phone": ["手机号码不合法"],
        "exam_id": ["已报名该场考试", "超出了最大修改次数"]
      }
    }
    ```

   - HTTP 401

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```

  - HTTP 404

    ```json
    { "detail": "Not Found." }
    ```

  - HTTP 429

    ```json
    { "detail": "rate limit" }
    ```



### 开始考试 - 环境检查

#### Request

- Method: **GET**
- URL: `/api/my-onlineexam/{slug}/env-check`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)


#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success"
    }
    ```

- Failed

   - HTTP 401 (未登录)

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```

  - HTTP 403

    ```json
    {
        "detail": "考试设置了 IP 白名单, 必须在指定环境下参加考试",
        "errcode": 0,  //  0 -> 考试设置了 IP 白名单, 必须在指定环境下参加考试
                       //  1 -> 不在考试限定时间范围内
                       //  2 -> 已交卷, 无法查看
    }
    ```

  - HTTP 404 (考试不存在)

    ```json
    { "detail": "Not Found" }
    ```


  - HTTP 429 (操作频繁被限制)

    ```json
    { "detail": "rate limit" }
    ```



### 开始考试 - 人脸识别

#### Request

- Method: **POST**
- URL: `/api/my-onlineexam/{slug}/face-identification`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- Body:

  ```json
    {
      "image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQA.....", // 图片的 Base64 编码, 只接受 jpg/png 格式, 大小限制在 3M 以下
    }
    ```

#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success"
    }
    ```

- Failed

  - HTTP 400

    ```json
    {
        "detail": "params error",
        "errors": {
            "image": ["图片格式错误", "文件大小超过 3M 限制"]
        }
    }
    ```

   - HTTP 401 (未登录)

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```

  - HTTP 403

    ```json
    {
        "detail": "考试设置了 IP 白名单, 必须在指定环境下参加考试",
        "errcode": 0,  //  0 -> 考试设置了 IP 白名单, 必须在指定环境下参加考试
                       //  1 -> 不在考试限定时间范围内
                       //  2 -> 已交卷, 无法查看
    }
    ```

  - HTTP 404 (考试不存在)

    ```json
    { "detail": "Not Found" }
    ```


  - HTTP 429 (操作频繁被限制)

    ```json
    { "detail": "rate limit" }
    ```



### 开始考试 - 获取试题及草稿

#### Request

- Method: **GET**
- URL: `/api/my-onlineexam/{slug}/paper`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- 说明: 只有在通过身份校验以及在考试时间范围内才可以调用本接口


#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success",
        "data": {
            "name": "TiDB DBA 认证考试 PCTA",
            "slug": "PCTA-2020-09-01",
            "short_description": "在线笔试, 通过后获得 PCTA 证书",  // 考试简要说明
            "detailed_description": "", // 考试详细说明, MarkDown 格式
            "start_at": "2020-09-01 11:00:00",  // 开始时间
            "end_at": "2020-09-01 12:00:00",    // 结束时间
            "start_at_timestamp": 1596240000,   // 开始时间，时间戳
            "end_at_timestamp": 1601424000,     // 结束时间，时间戳
            "now": "2020-09-21 13:55:17",       // 当前时间
            "now_timestamp": 1600667717,        // 当前时间，时间戳
            "rules": "考试规则",        // 考试规则
            "number_of_questions": 60,  // 试题数量
            "total_score": 60, // 总分
            "passing_score": 30, // 及格线
            "structure": [{
                "title": "一、单选题",
                "description": "",
                "number_of_questions": 10,
                "questions": [{
                    "id": "Q_1", // 试题 ID
                    "type": 0,  // 试题类型: 0 -> 单选题,  1 -> 多选题
                    "score": 1,  // 分数
                    "problem": "关于TiDB 的索引 Key 编码，下列哪一项是对的？",  // 问题
                    "options": [
                        {
                            "value": "O_1",
                            "text": "需要 Mem compareble 的编码算法"
                        },
                        {
                            "value": "O_2",
                            "text": "索引 Key 中一定包含 RowID"
                        },
                        {
                            "value": "O_3",
                            "text": "TiDB 支持对索引 Key 进行逆序编码，以支持逆序索引"
                        }
                    ]
                }]
            }, {
                "title": "二、多选题",
                "description": "",
                "number_of_questions": 20,
                "questions": [{
                    "id": "Q_2", // 试题 ID
                    "type": 1,  // 试题类型: 0 -> 单选题,  1 -> 多选题
                    "score": 1,  // 分数
                    "problem": "TiDB 集群建议使用的 Linux 操作系统是？",  // 问题
                    "options": [
                        {
                            "value": "O_12",
                            "text": "Red Hat Enterprise Linux"
                        },
                        {
                            "value": "O_2",
                            "text": "CentOS"
                        },
                        {
                            "value": "O_3",
                            "text": "Debian Linux"
                        }
                    ]
                }]
            }],
            "draft": {          // 草稿, 恢复用户之前已完成的答案
                "Q_1": "O_1",   // Key 为试题 ID, value 为答案
                "Q_2": "O_1",   // 单选题答案
                "Q_3": ["O_1", "O_2"],  // 多选题答案
            }
        }
    }
    ```

- Failed

   - HTTP 401

    ```json
    { "detail": "未登录" }
    ```

  - HTTP 403

    ```json
    {
        "detail": "考试设置了 IP 白名单, 必须在指定环境下参加考试",
        "errcode": 0,  //  0 -> 考试设置了 IP 白名单, 必须在指定环境下参加考试
                       //  1 -> 不在考试限定时间范围内
                       //  2 -> 已交卷, 无法查看
    }
    ```

  - HTTP 404

    ```json
    { "detail": "not found" }
    ```

  - HTTP 429

    ```json
    { "detail": "rate limit" }
    ```



### 考试中 - WebSocket 通信

#### 说明

- URL: `wss://{host}/ws/online-exam/{exam_slug}`
- 用途: 主要用于服务端下发消息通知
- 建立连接: 进入试卷页面成功获取试卷后，开始建立连接
- 交卷后断开连接
- 消息格式:
  ```json
  {
    "event": "event_type",
    "data": {},
    "sequence_id": 1, // 消息 ID, 客户端根据此 ID 进行 ACK
  }
  ```
- 客户端 ACK (应用层面的消息确认机制), 如果服务端在消息发送后 1 分钟内没有接到 ACK, 会尝试再次发送该消息(连续三次尝试失败后断开连接), ACK 格式如下:
  ```json
  {
    "event": "ack",
    "sequence_id": 1
  }
  ```
- 客户端心跳保持, 客户端应每 3 分钟主动与服务端进行一次 Ping-Pong, 以避免 NAT 以及负载均衡导致的连接断开
  - 客户端 Ping:
    ```json
    {
      "event": "ping",
      "sequence_id": 2, // 客户端自定义, 服务端会原样返回
    }
    ```
  - 服务端 Pong:
    ```json
    {
      "event": "pong",
      "sequence_id": 2
    }
    ```
- 对于每位用户，服务端在同一时间只允许建立一个连接，如果建立多个连接，服务端会主动断开所有的连接，详见下面的 `考试暂停` 消息说明

#### 消息类型及说明

- 警告
  - 消息内容
  ```json
  {
    "event": "warning_message",
    "data": {
        "message": "摄像头内没有检测到考生，请调整坐姿"
    },
    "sequence_id": 1
  }
  ```
  - 服务端用途: 抓拍照片的异步检测结果、监考老师的通知
  - 客户端处理: 弹出模态框，提示考生

- 身份校验失败, 补全信息
  - 消息内容
  ```json
  {
    "event": "identity_failed_complete_information",
    "data": {
        "name": "张三",
        "chinese_id_number": "6104281111111111"
    },
    "sequence_id": 1
  }
  ```
  - 服务端用途: 身份校验失败，要求考生重新确认信息后再次进行校验
  - 客户端处理: 弹出模态框，要求考生确认并修改

- 强制结束考试
  - 消息内容
  ```json
  {
    "event": "force_submit",
    "data": {
      "message": "考生违规说明"
    },
    "sequence_id": 1
  }
  ```
  - 服务端用途: 监考老师强制结束考试
  - 客户端处理: 立刻强制交卷

- 考试暂停
  - 消息内容
  ```json
  {
    "event": "force_suspension",
    "data": {
      "message": "只允许打开一个考试页面"
    },
    "sequence_id": 1
  }
  ```
  - 服务端用途: 服务端监测到多个客户端尝试建立连接，返回该消息后，会主动断开所有连接
  - 客户端处理: 停止考试并且不应该尝试重连，由考生确认后只在一个客户端上恢复连接



### 考试中 - 考生身份信息修改

#### Request

- Method: **POST**
- URL: `/api/my-onlineexam/{slug}/examinee-info`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- 说明: 只有在通过身份校验以及在考试时间范围内才可以调用本接口
- Body:
  ```json
  {
    "name": "张三",
    "chinese_id_number": "1111111111111111111"
  }
  ```

#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success",
    }
    ```

- Failed

   - HTTP 400

    ```json
    {
        "detail": "params wrong",
        "errors": {
          "name": ["不能为空"],
          "chinese_id_number": ["格式错误"]
        }
    }
    ```

   - HTTP 401

    ```json
    { "detail": "未登录" }
    ```

  - HTTP 403

    ```json
    {
        "detail": "考试设置了 IP 白名单, 必须在指定环境下参加考试",
        "errcode": 0,  //  0 -> 考试设置了 IP 白名单, 必须在指定环境下参加考试
                       //  1 -> 不在考试限定时间范围内
                       //  2 -> 已交卷, 无法查看
    }
    ```

  - HTTP 404

    ```json
    { "detail": "not found" }
    ```

  - HTTP 429

    ```json
    { "detail": "rate limit" }
    ```




### 考试中 - 保存草稿

#### Request

- Method: **POST**
- URL: `/api/my-onlineexam/{slug}/draft`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- 说明: 只有在通过身份校验以及在考试时间范围内才可以调用本接口
- Body:

  ```json
    {
        "Q_1": "O_1",   // Key 为试题 ID, value 为答案
        "Q_2": "O_1",   // 单选题答案
        "Q_3": ["O_1", "O_2"],  // 多选题答案
    }
    ```


#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success",
    }
    ```

- Failed

   - HTTP 400

    ```json
    {
        "detail": "params wrong"
    }
    ```

   - HTTP 401

    ```json
    { "detail": "未登录" }
    ```

  - HTTP 403

    ```json
    {
        "detail": "考试设置了 IP 白名单, 必须在指定环境下参加考试",
        "errcode": 0,  //  0 -> 考试设置了 IP 白名单, 必须在指定环境下参加考试
                       //  1 -> 不在考试限定时间范围内
                       //  2 -> 已交卷, 无法查看
    }
    ```

  - HTTP 404

    ```json
    { "detail": "not found" }
    ```

  - HTTP 429

    ```json
    { "detail": "rate limit" }
    ```



### 考试中 - 交卷

#### Request

- Method: **POST**
- URL: `/api/my-onlineexam/{slug}/submit`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- 说明: 只有在通过身份校验以及在考试时间范围内才可以调用本接口, 交卷后考试结束
- Body:

  ```json
    {
        "answers": {
            "Q_1": "O_1",   // Key 为试题 ID, value 为答案
            "Q_2": "O_1",   // 单选题答案
            "Q_3": ["O_1", "O_2"],  // 多选题答案
        },
        "is_force_submitted": false,  // 是否因犯规被强制交卷
    }
    ```


#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success"
    }
    ```

- Failed

   - HTTP 400

    ```json
    {
        "detail": "params wrong"
    }
    ```

   - HTTP 401

    ```json
    { "detail": "未登录" }
    ```

  - HTTP 403

    ```json
    {
        "detail": "考试设置了 IP 白名单, 必须在指定环境下参加考试",
        "errcode": 0,  //  0 -> 考试设置了 IP 白名单, 必须在指定环境下参加考试
                       //  1 -> 不在考试限定时间范围内
                       //  2 -> 已交卷, 无法查看
    }
    ```

  - HTTP 404

    ```json
    { "detail": "not found" }
    ```

  - HTTP 429

    ```json
    { "detail": "rate limit" }
    ```



### 考试中 - 考生违章抓拍

#### Request

- Method: **POST**
- URL: `/api/my-onlineexam/{slug}/examinee-capture`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- Body:

  ```json
    {
      "image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQA.....", // 图片的 Base64 编码, 只接受 jpg/png 格式, 大小限制在 3M 以下
    }
    ```

#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success"
    }
    ```

- Failed

  - HTTP 400

    ```json
    {
        "detail": "params error",
        "errors": {
            "image": ["图片格式错误", "文件大小超过 3M 限制"]
        }
    }
    ```

   - HTTP 401 (未登录)

    ```json
    { "detail": "Authentication credentials were not provided." }
    ```

  - HTTP 403

    ```json
    {
        "detail": "考试设置了 IP 白名单, 必须在指定环境下参加考试",
        "errcode": 0,  //  0 -> 考试设置了 IP 白名单, 必须在指定环境下参加考试
                       //  1 -> 不在考试限定时间范围内
                       //  2 -> 已交卷, 无法查看
    }
    ```

  - HTTP 404 (考试不存在)

    ```json
    { "detail": "Not Found" }
    ```

  - HTTP 429 (操作频繁被限制)

    ```json
    { "detail": "rate limit" }
    ```



### 考试中 - 行为上报

#### Request

- Method: **POST**
- URL: `/api/my-onlineexam/{slug}/event`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- Body:

  ```json
    {
      "event": "ExitFullScreen",  // 事件类型: ExitFullScreen(退出全屏)、LostFocus(失去焦点)
      "version": 1,  // 事件版本号, 当前固定为 1
    }
    ```

#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success",
        "data": {
            "force_submit": true,  // 是否强制交卷
        }
    }
    ```



### 测试 - 考试状态重置

#### Request

- Method: **GET**
- URL: `/api/my-onlineexam/debug/reset`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- 说明: 仅测试环境可调用


#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success"
    }
    ```



### 测试 - 设置报名阶段考试状态

#### Request

- Method: **POST**
- URL: `/api/my-onlineexam/debug/set-faceid-result`
- Content-Type: application/json
- Permission: IsAuthenticated (已登录)
- 说明: 仅测试环境可调用
- Body:

  ```json
    {
      "status": 1,
    }
    ```


#### Response Example

- Success
  - HTTP 200

    ```json
    {
        "detail": "success"
    }
    ```
