fix(portal): 补齐写操作的必填 body(风控升级/解决、场外六个写接口)

- 风控:escalations 要 reason、resolutions 要 resolution(字段名不同),
  且真实顺序是 先确认接收 才能升级/解决(否则 409 请先确认接收预警)
- 场外:六个写接口都必填 operator_id(防伪校验,须等于当前登录用户),
  confirmations 还要 decision(中文枚举)、notifications 还要 notification_type、
  recalculate 要 fund_code+application_date;门户自动带当前 user_id
- 实测:recalculate 200 code=0;不传 operator_id 必得 422(证明该字段必须)
- 清单 §3/§4 更新为实测结果并列出各写接口的必填字段表
This commit is contained in:
2026-09-12 16:08:38 +08:00
parent 552055ee2d
commit 76923d7e8b
2 changed files with 80 additions and 26 deletions
+22 -9
View File
@@ -79,8 +79,8 @@ D:\conda\envs\jr_py313\python.exe tools\portal.py --base-url http://127.0.0.1:80
| 3-3 | 进入即自动加载"预警列表" | **表格出现**:预警号 / 客户 / 等级 / 规则 / 状态 / 操作。本机实测 2 条:`ALDEMO0002`(高,RW-015/RW-003)、`ALDEMO0001`(中,RW-007/RW-002/RW-012),均"待处理" ✅实测<br>⚠️ **门户刻意不传 `limit`**:该接口 `limit` 上限是 **5**,传 20 会得到 `422 query.limit: Input should be less than or equal to 5`,**整张表格渲染不出来**(行内按钮也随之消失) |
| 3-4 | 点「触发一次扫描」 | **HTTP 200,`body.code=0`**(这条以前会因缺幂等头报 422,已修) ✅实测 |
| 3-5 | 点「生成日报」 | HTTP 200,返回日报内容 ⚠️按契约 |
| 3-6 | 对某条预警点「确认」 | 二次确认后返回业务结果。✅实测:`POST .../acknowledgements` → **409「只有待处理的预警才能确认解决」** —— 该动作**有状态前置条件**,不是任意状态都能点,属正常业务约束(页面会原样显示原因) |
| 3-7 | 点「升级」/「解决」 | 同上,各自有状态约束;状态不符时返回 409 ⚠️按契约 |
| 3-6 | 对某条预警点「确认」 | 二次确认后返回业务结果。✅实测:`POST .../acknowledgements`(**无必填 body**)→ **409「只有待处理的预警才能确认解决」** —— 该动作**有状态前置条件**,不是任意状态都能点 |
| 3-7 | 点「升级」/「解决」 | ✅实测:**必须先「确认」接收预警**,否则两者都返回 **409「请先确认接收预警」**。<br>升级要填 <code>reason</code>、解决要填 <code>resolution</code>(**字段名不同**,各 1-500 字),门户已做成弹窗必填;不填会是 422 |
| 3-8 | 点一个**不存在**的预警号 | 404 资源不存在 —— 正常 fail closed ⚠️按契约 |
| 3-9 | 用**客户**账号访问风控接口 | **403 缺少操作权限**(`risk_t` 能看,`cust_t` 不能) ✅实测(客户访问 `/admin/roles` 为 403) |
@@ -88,7 +88,8 @@ D:\conda\envs\jr_py313\python.exe tools\portal.py --base-url http://127.0.0.1:80
> 这是种子设计如此,不是越权漏洞。
>
> ⚠️ 行内三条按钮对应 `acknowledgements` / `escalations` / `resolutions` 三个端点,都是**写操作**:
> 会在库里留数据与审计,且**受状态机约束**。列表拿不到数据时这三个按钮不会出现 ——
> 会在库里留数据与审计,且**受状态机约束**(**确认 → 升级/解决**)。三个动作的请求体各不相同:
> 确认无 body、升级要 `reason`、解决要 `resolution`。列表拿不到数据时这三个按钮不会出现 ——
> 先确认 3-3 是否正常。
---
@@ -98,14 +99,26 @@ D:\conda\envs\jr_py313\python.exe tools\portal.py --base-url http://127.0.0.1:80
| # | 操作 | 预期结果 |
|---|---|---|
| 4-1 | 进入即加载"邮箱状态" | 数字卡片,HTTP 200 ✅实测 |
| 4-2 | 点「拉取邮件列表」 | 表格(邮件 ID / 主题 / 状态 / 操作);**邮箱未配置时**返回空列表或错误说明,不是白屏 ✅实测(HTTP 200) |
| 4-2 | 点「拉取邮件列表」 | 表格(邮件 ID / 主题 / 状态 / 操作)。**邮箱未配置时条数为 0**、不白屏 ✅实测(HTTP 200,0 条) |
| 4-3 | 点某封邮件的「识别字段」 | 返回识别结果 JSON ⚠️按契约(需库里有邮件数据) |
| 4-4 | 点「删除」邮件 | 二次确认后写操作;审计留痕 ⚠️按契约 |
| 4-5 | 点「触发邮箱恢复」 | 二次确认后 HTTP 200 ⚠️按契约 |
| 4-4 | 点「删除」邮件 | 二次确认后写操作。门户自动带 `operator_id`(= 当前登录 user_id)✅实测(字段齐了才会进业务层) |
| 4-5 | 点「触发邮箱恢复」 | ✅实测:`HTTP 200 + body.code=404「邮箱尚未初始化」` —— 请求体已合法,是本机没配邮箱,属正常 |
| 4-6 | 在"单据处理"填一个**真实存在**的 task_id,点「识别字段」「规则结果」 | 返回该单据的字段与规则判定 ⚠️按契约 |
| 4-7 | 填一个**不存在**的 task_id | 404 资源不存在,页面上原样显示 —— 正常 ⚠️按契约 |
| 4-8 | 点「确认单据」/「重试识别」/「创建通知」 | 二次确认后写操作;审计留痕 ⚠️按契约 |
| 4-9 | 点「重算结算统计」 | 二次确认后 HTTP 200 ⚠️按契约 |
| 4-7 | 填一个**不存在**的 task_id | 422/404,页面上原样显示 —— 正常 ⚠️实测(假 task_id 得到 422) |
| 4-8 | 点「确认单据」/「重试识别」/「创建通知」 | 见下方"三个写操作的必填字段",各自会弹窗要必填值 ⚠️按契约 |
| 4-9 | 点「重算结算统计」 | ✅实测:要填 `fund_code` + `application_date`(门户已弹窗),**HTTP 200 `code=0` ok** |
> **场外写操作的三条硬约束**(实测得出,门户已按此实现):
>
> | 接口 | 必填字段 |
> |---|---|
> | `mailbox-status/recoveries`、`mails/{id}/deletions`、`documents/{id}/recognition-retries` | `operator_id` |
> | `documents/{id}/confirmations` | `decision`(**中文枚举**:确认无误 / 确认异常 / 未处理)+ `operator_id` |
> | `documents/{id}/notifications` | `notification_type`(risk / settlement / mail_return / normal_return / exception_return…)+ `operator_id` |
> | `settlement-statistics/recalculate` | `fund_code` + `application_date`(**没有** operator_id) |
>
> `operator_id` 是**防伪校验**:平台会核对它是否等于当前登录用户(传别人的会被拒)。
> 实测不传它必得 **422**,所以门户一律自动带本次登录的 user_id,不让你手填。
> **运营为什么只有 2 项权限却能用**:场外线的服务层用的是**角色门槛**
> `{"operator","risk_operator","admin","super_admin"}`(`offsite_fund_service.py:2600`),