17 KiB
前端验收清单(统一登录门户)
目的:把门户的每一个功能都走一遍,逐项对照"预期结果"判断是否符合预期。 被测对象:
tools/portal.py(统一登录门户),默认 http://127.0.0.1:8101 时点:2026-09-12 证据口径:标 ✅实测 的项是本轮真实跑过并确认的;标 ⚠️按契约 的项是照接口定义推断的 (沙箱里不便反复写业务数据,留给你点的时候确认)。
0. 启动与前置
0.1 前置(不满足会直接显示原因,不会静默)
| # | 项 | 命令 / 检查 | 预期 |
|---|---|---|---|
| 0-1 | 依赖服务 | MySQL、Redis 可用(Docker Desktop 要在跑,Milvus 才可用) | 门户顶部不出现"平台初始化失败"红条 |
| 0-2 | 演示账号 | python tools/seed_test_rbac.py 然后 python tools/set_user_password.py |
五个账号都能登录;口令脚本非幂等,重复执行等于重设密码 |
| 0-3 | 停常驻 Worker | 确认没有 python -m app.worker 在跑 |
否则客服对话会被抢队列,页面一直转圈 |
0.2 启动
D:\conda\envs\jr_py313\python.exe tools\portal.py # 进程内直挂平台,走真实鉴权栈
D:\conda\envs\jr_py313\python.exe tools\portal.py --base-url http://127.0.0.1:8000
- 打开 http://127.0.0.1:8101 → 预期:出现登录卡片,标题"基金智能服务平台"
- 页面顶部右侧显示连接环境 → 预期:
进程内 · 127.0.0.1:3306/jr(口令已脱敏成***) - 用错的密码登录 → 预期:红条提示
登录失败(HTTP 200):...,停在本页 ✅实测 - 同一个浏览器开两个标签页,分别登录客户与管理员 → 预期:互不干扰(会话号存
sessionStorage)
1. 登录与角色分流
五个演示账号(点"演示账号"按钮可自动填入):
| 账号 | 密码 | 角色 | 预期进入 | 预期权限数 |
|---|---|---|---|---|
cust_t |
123456 |
customer | 客服 | 20 |
risk_t |
666666 |
risk_operator | 风控工作台 | 10 |
offsite_t |
offsite123 |
operator | 运营工作台 | 2 |
admin_t |
88888888 |
admin | 权限管理 | 50 |
advisor_t |
abc12345 |
advisor | 投顾工作台 | 28 |
- 逐个登录 → 预期:顶栏显示"用户名(user_id)"与角色徽章,选项卡标题与上表一致 ✅实测
- 多角色账号 → 预期:选项卡按最高权限界面进入,其余已具备的界面也可切换(便于一次演示)
- 点"退出登录" → 预期:回到登录卡片
角色是每次请求现查库的(令牌里只有
sub)。改了库里角色,重新登录即生效,不用重启门户。
2. 客户 · 客服视图(cust_t)
| # | 操作 | 预期结果 |
|---|---|---|
| 2-1 | 输入"赎回基金多久到账?"发送 | 气泡里出现回答(如"货币基金T+0或T+1到账…QDII T+7、T+10"),下方标签显示意图(如 faq) ✅实测 |
| 2-2 | 看回答末尾 | 附合规提示:"本内容仅为投资风险参考,不构成任何直接投资建议…" ✅实测 |
| 2-3 | 问一个答不了的(如"帮我下单") | Agent 引导拨打客服热线;若判定需人工,标签显示已转人工 ✅实测(问"赎回费怎么算"得到引导话术) |
| 2-4 | 点「我的画像」 | 返回当前客户的记忆画像(HTTP 200) ✅实测 |
| 2-5 | 点「我的画像候选」 | 返回候选列表;没有候选时显示"暂无候选(HTTP 200)"而不是报错 ⚠️按契约 |
| 2-6 | 候选里点「确认」/「拒绝」 | HTTP 200,状态变更;再刷新列表状态已更新 ⚠️按契约(本机暂无候选数据) |
| 2-7 | 点「转人工」 | 弹出说明输入框 → 确认后 HTTP 202,返回 handover_id 与 status=pending ✅实测 |
| 2-8 | 转人工后,用管理员看"客服转人工工单" | 新工单出现在队列里 ✅实测(ticket-d8c526a9…) |
2-7 的实现细节:门户会先调
POST /api/v1/conversations真实建会话(201)再转人工。 早期版本用自己编的 session id,会得到 404「会话不存在」。
3. 员工 · 风控工作台(risk_t)
本节按
docs/风控业务演示文档/17-风控模块-前端合并提示词与验收约束.md重写过,功能清单与交互约束都对齐了那份文档。
| # | 操作 | 预期结果 |
|---|---|---|
| 3-1 | 进入即自动加载"风险概览" | 五张指标卡:预警总量 / 待处理 / 已超时 / 高风险 / 重点预警 ✅实测(total=2 pending=1 overdue=2 高风险=2 重点=2) |
| 3-2 | 看"预警队列" | 每页 5 条、按风险等级优先、行高固定且超长省略 ✅实测 本机 2 条: ALDEMO0002(高,RW-015/RW-003)、ALDEMO0001(高,RW-007/RW-002/RW-012)⚠️ 该接口 limit 上限就是 5,传 20 会 422 且整张表渲染不出来 |
| 3-3 | 用筛选栏逐项筛 | 8 个条件:关键词、客户号、风险等级、规则码、产品代码、产品名、起止时间 ✅实测rule_code=RW-015 → ALDEMO0002、customer_no=T-CUST → 2 条、keyword=ALDEMO0002 → 1 条⚠️ 风险等级必须填「高/中/低」:预警对象用「高」,而概览 levels 用「高风险」,填「高风险」会 422 |
| 3-4 | 点「下一页 / 上一页」 | 走游标分页(meta.next_cursor + has_more),页脚显示"第 N 页 · 每页 5 条" ✅实测(本机 2 条 → 已到末页) |
| 3-5 | 点某行「详情」 | 弹窗显示:预警编号、类型、等级与优先级分、处置状态、回执状态、证据归档、规则、到期时间、结案原因、证据摘要、证据快照、客户(行为分/投资者类型/风险标签)✅实测(alert 23 字段、customer 13 字段) |
| 3-6 | 弹窗里点「确认接收」 | 二次确认后提交。✅实测:POST .../acknowledgements(无 body)→ 409「只有待处理的预警才能确认解决」,说明该动作有状态前置 |
| 3-7 | 弹窗里点「进入调查」 | 二次确认后 POST .../investigations(无 body)⚠️按契约 |
| 3-8 | 弹窗里点「关闭误报」 | 必须填写理由(1-500 字),空值会被前端与后端双重拒绝 → POST .../exclusions {reason} ✅实测(body 字段已核对) |
| 3-9 | 弹窗里点「升级」 | 必须先「确认接收」,否则 409「请先确认接收预警」;填 reason → POST .../escalations ✅实测 |
| 3-10 | 弹窗里点「完成结案」 | 填 resolution(字段名不是 reason)→ POST .../resolutions ✅实测 |
| 3-11 | 点「手动扫描」 | 二次确认后 POST /alerts/scan → HTTP 200, code=0 ✅实测 |
| 3-12 | 点「生成日报」 | 弹窗内流式生成(SSE:start / progress / replace),生成后可编辑内容再填收件人发送 ✅实测(事件类型已核对) |
| 3-13 | 点「八类证据」 | 八个页签:客户 / 产品 / 交易 / 资金 / 持仓 / 登录 / 预警 / 通知,各带关键词、行为分等级、发送状态、起止时间筛选 ✅实测(8/8 全部 HTTP 200) ⚠️ 正确路径是 holdings,写成 positions 会被 422 拒绝 |
| 3-14 | 点「通知记录」 | 表格:预警编号 / 类型 / 发送状态 / 时间 ✅实测(HTTP 200) |
| 3-15 | 用客户账号访问风控接口 | 403 缺少操作权限 ✅实测 |
⚠️ 注意:
risk_t有audit:read,所以它访问/api/v1/admin/roles是 200 而不是 403 —— 这是种子设计如此,不是越权漏洞。⚠️ 五个处置动作都是写操作,会在库里留数据与审计,且受状态机约束(先确认 → 再调查/误报/升级/结案)。 各自动作的请求体不同:确认与进入调查无 body、误报与升级要
reason、结案要resolution。 列表拿不到数据时行内按钮不会出现 —— 先确认 3-2 是否正常。⚠️ 排序局限:接口没有排序参数,门户只在当前页内按风险等级+优先级分排序;跨页整体排序需要服务端支持。
⚠️ 本轮顺带修掉一个平台 bug:
rule_code筛选此前恒返回 0 条(risk_repository.py用.contains([code]),SQLAlchemy 把它编译成LIKE,而列是 JSON 数组,等于匹配字符串'["RW-015"]')。已改为func.json_contains(col, json.dumps(code)),扫描去重处的同一写法也一并修了。
4. 员工 · 运营工作台(offsite_t)
| # | 操作 | 预期结果 |
|---|---|---|
| 4-1 | 进入即加载"邮箱状态" | 数字卡片,HTTP 200 ✅实测 |
| 4-2 | 点「拉取邮件列表」 | 表格(邮件 ID / 主题 / 状态 / 操作)。邮箱未配置时条数为 0、不白屏 ✅实测(HTTP 200,0 条) |
| 4-3 | 点某封邮件的「识别字段」 | 返回识别结果 JSON ⚠️按契约(需库里有邮件数据) |
| 4-4 | 点「删除」邮件 | 二次确认后写操作。门户自动带 operator_id(= 当前登录 user_id)✅实测(字段齐了才会进业务层) |
| 4-5 | 点「触发邮箱恢复」 | ✅实测:HTTP 200 + body.code=404「邮箱尚未初始化」 —— 请求体已合法,是本机没配邮箱,属正常 |
| 4-6 | 在"单据处理"填一个真实存在的 task_id,点「识别字段」「规则结果」 | 返回该单据的字段与规则判定 ⚠️按契约 |
| 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-retriesoperator_iddocuments/{id}/confirmationsdecision(中文枚举:确认无误 / 确认异常 / 未处理)+operator_iddocuments/{id}/notificationsnotification_type(risk / settlement / mail_return / normal_return / exception_return…)+operator_idsettlement-statistics/recalculatefund_code+application_date(没有 operator_id)
operator_id是防伪校验:平台会核对它是否等于当前登录用户(传别人的会被拒)。 实测不传它必得 422,所以门户一律自动带本次登录的 user_id,不让你手填。
运营为什么只有 2 项权限却能用:场外线的服务层用的是角色门槛
{"operator","risk_operator","admin","super_admin"}(offsite_fund_service.py:2600), 不是权限码。那 2 项是offsite:write和financial:nl2sql:read。 换句话说:"看不到运营界面"以前是前端没做,不是权限问题。
5. 管理员 · 权限管理(admin_t)
5.1 角色与权限
| # | 操作 | 预期结果 |
|---|---|---|
| 5-1 | 进入即加载角色卡片 | 每个角色显示 权限数 / 角色名 / user_count ✅实测(GET /admin/roles 200) |
| 5-2 | 点任一角色卡片 | 列出该角色的权限码(药丸标签)+ 角色详情 ✅实测(customer 20 项) |
| 5-3 | 对照第 1 节的权限数 | 与登录时顶栏显示的权限数一致 |
| 5-4 | 输入 9001 点「查询该用户的角色」 |
返回 cust_t → customer 的解析结果 ✅实测 |
平台只提供只读查询:改权限要发布新的
config_release,没有直接写接口 —— 这是设计(配置受版本控制),不是功能没做完。页面上也这么写了。
5.2 审计与工单
| # | 操作 | 预期结果 |
|---|---|---|
| 5-5 | 点「刷新」审计流水 | 时间 / 动作 / 操作者 / 结果;做过写操作后能看到刚才那条 ✅实测(200) |
| 5-6 | 点「刷新工单」 | 客户在第 2-7 步建的工单出现在这里(工单号 / 来源 / 优先级 / 原因 / 状态)✅实测 |
| 5-7 | 点「列出知识文档」 | 文档清单(ID / 标题 / 状态)⚠️按契约(需 KB 有数据) |
5.3 推广材料审核
| # | 操作 | 预期结果 |
|---|---|---|
| 5-8 | 填投顾给你的任务单号,点「查任务」 | 返回任务详情,若已有 approved/sent 版本会自动填进版本号框 ✅实测 |
| 5-9 | 填 material_version_id,点「通过」 |
二次确认后 HTTP 200 code=0;此后投顾才能投递 ✅实测(版本 19 通过) |
| 5-10 | 点「退回修改」/「拒绝」 | 同样 200,状态流转 ⚠️按契约 |
6. 投顾 · 投顾工作台(advisor_t)
6.1 投资目标与方案
| # | 操作 | 预期结果 |
|---|---|---|
| 6-1 | 客户 ID 填 9001,点「查投资目标」 |
200,返回目标(目标区间、基准)—— 前提是 9001 在你名下且已建过目标 ✅实测(4.5000 - 8.0000、沪深300) |
| 6-2 | 改填一个不在你名下的客户 ID | 404「客户不可访问」 —— 最小权限,data_scope=own_customers 生效 ✅实测设计如此 |
| 6-3 | 点「已发布方案」 | 200,返回已发布方案列表 ✅实测 |
| 6-4 | 点「跑组合分析」 | 二次确认后 200(请求体是空对象 {},多传字段会 422)✅实测 |
| 6-5 | 点「生成资产配置」 | 同上 ✅实测 |
6.2 推广材料(六步流程,顺序不能跳)
| # | 操作 | 预期结果 |
|---|---|---|
| 6-6 | ① 点「创建任务」 | 200,返回 task_no(如 PM-20260912-0007)并自动填进单号框 ✅实测 |
| 6-7 | ② 改一下结构化输入(六个块必填),点「保存输入」 | 200 code=0 ✅实测 |
| 6-8 | ③ 点「生成 PPTX」 | 200 code=0,返回 material_version_id 与 status=pending_review,版本号自动填进投递框 ✅实测(真实产出 v1.pptx) |
| 6-9 | 若没填费率就点生成 | body.code=422「材料内容未通过合规校验」,findings 里是 fee_structure.incomplete(severity=block),任务被置为 compliance_failed ✅实测 |
| 6-10 | ④ 让管理员在 5.3 审核通过 | 见 5-9 |
| 6-11 | ⑤ 点「投递」(投顾 id 填 9020) |
200 code=0 ✅实测 |
| 6-12 | ⑥ 点「查询任务」 | 200,status="sent",含 material_version(版本号、pptx 路径)✅实测 |
| 6-13 | 没投递就查询 | 404「该材料尚未发送给当前投顾」 —— 这是合规设计,不是故障;页面会追加提示告诉你怎么走完 ✅实测 |
| 6-14 | 点「合规检查结果」 | 列出 findings(通过的会显示 overall.pass)✅实测 |
⚠️ 两条必须知道的约定:
- 费率七项必须非空(
subscription_fee/purchase_fee/redemption_fee/sales_service_fee/management_fee/custody_fee/client_maintenance_fee),否则合规规则阻断生成。 骨架里填的是「待填写」占位,真实材料必须换成真实费率。- 骨架刻意不预填任何业绩数字:
performance_info的业绩字段全部可选且带show_product_performance开关,关掉即可 —— 编造业绩是红线。
7. 通用行为预期(跨视图)
| # | 情形 | 预期表现 |
|---|---|---|
| 7-1 | 业务失败也返回 HTTP 200 | 本平台把业务错误放在 body.code(如生成失败 HTTP 200 + code=422)。门户按 body.code 判成败并标红 ✅实测 |
| 7-2 | 403 | "当前角色权限不足(平台按设计 fail closed)",原样显示不隐藏 ✅实测 |
| 7-3 | 404 | 显示 message。注意 SESSION_NOT_FOUND 被三个异常类共用,可能是"会话不存在"、"客户不可访问"或"知识文档不存在",只能看 message 区分 ✅实测 |
| 7-4 | 422 | 报文格式问题,error.field_errors 会指出具体字段 ✅实测 |
| 7-5 | 写操作 | 一律二次确认(confirm),避免误点改数据 ✅实测 |
| 7-6 | 令牌 | 只存在服务端,浏览器拿不到;前端只有一个随机会话号 ✅设计 |
| 7-7 | 空数据 | 显示"暂无…(HTTP xxx)"或原始返回,不白屏 ✅实测 |
8. 已知限制(先说明,免得当成 bug)
- 权限界面只读 —— 平台没有写接口,改权限走
config_release发布; - 运营工作台的"单据处理"需要一个真实的 task_id —— 本机没有场外单据数据时,识别/确认/通知只能看到 404,属正常;
- 组合分析与资产配置只对空请求体有效(
{},additionalProperties:false); - 风控日报/处置类写操作会在库里留数据与审计,验收时建议用专用库或事后清理;
- 门户是单进程工具:会话存内存,重启门户需要重新登录。
9. 验收记录表
| 章节 | 项数 | 通过 | 不符合预期 | 备注 |
|---|---|---|---|---|
| 0 启动与前置 | 4 | |||
| 1 登录与分流 | 3 | |||
| 2 客户 · 客服 | 8 | |||
| 3 员工 · 风控 | 9 | |||
| 4 员工 · 运营 | 9 | |||
| 5 管理员 · 权限 | 10 | |||
| 6 投顾 · 投顾台 | 14 | |||
| 7 通用行为 | 7 | |||
| 合计 | 64 |
发现不符合预期的项,记下章节号 + 当时的
trace_id(响应里带),可以直接定位到那一次请求。