DTL · L-20260825-05¶
文档编号:DOC-A04-DTL / L-20260825-05 维护人:听写 DT(DOC-A01 PMG V8.3 §人机协作模型) 关联 DTL:L-20260825-04 · L-20260825-01 撰写日期:2026-08-25 任务:梳理后端未就绪前的本地 Mock 策略——纯前端 issueMockJwt() → 5 端登录页一键 Mock 登录
一、任务背景¶
WP-04/05/07 等 P1 工作包依赖后端 /api/v2/* 接口就绪,但后端 BFF 层排期未定。用户 2026-08-25 提出:能否先在本地测试环境下,模拟不同角色(师傅、客户等)的后端返回数据?
核心洞察(来自 auth.ts parseWhoamiFromJwt 实现):
前端 WhoamiProvider 仅解析 JWT payload 段(Base64URL),完全不验签。因此可在前端纯 JS 生成合法格式的 JWT(三段式 header.payload.mock_sig),WhoamiProvider 与 API client 均可正常工作,无需任何后端进程。
基于此洞察,执行 4 项改造:纯前端 mock JWT 签发函数 + 3 Web 端登录页按钮组 + 2 RN 端登录屏 + 本操作手册。
二、交付文件清单¶
2.1 修改文件(6)¶
| 文件(相对项目根) | 变更说明 |
|---|---|
dip1/frontend/packages/shared/src/types/auth.ts |
新增 issueMockJwt(role, overrides) 函数 + ROLE_TO_APP / ROLE_BUTTON_META 常量 + b64UrlEncode/fallbackBtoa(btoa polyfill) |
dip1/frontend/apps/mfr-portal/app/login/page.tsx |
新增「🚀 一键 Mock 登录」区块:MFR-USR 高亮推荐 + 其他 5 角色 details 折叠;ppl_id 同时影响 Mock 和后端签发 2 条流程 |
dip1/frontend/apps/opr-admin/app/login/page.tsx |
新增 Mock 登录区块:OL+ML 双推荐(2 列)+ 其他 4 角色折叠 |
dip1/frontend/apps/lgp-portal/app/login/page.tsx |
新增 Mock 登录区块:LGP-USR 单推荐 + 其他 5 角色折叠 |
dip1/frontend/apps/wkr-app/App.tsx |
新增 MockLoginScreen(WKR 推荐 + 跨端角色)+ AppInner(未登录拦截→登录屏,已登录→原作业卡);顶栏新增 👤 display_name + wkr_id + 红色「退出」按钮 |
dip1/frontend/apps/cst-app/App.tsx |
同 WKR 改造:新增 Mock 登录屏(CST 推荐)+ AppInner + 顶栏 👤 display_name + cst_id + 退出按钮 |
2.2 核心 API 签名¶
// packages/shared/src/types/auth.ts
export function issueMockJwt(
role: RoleCode, // "CST" | "WKR" | "MFR-USR" | "LGP-USR" | "OL" | "ML"
overrides?: Partial<JwtPayload & { display_name?: string }>,
): string;
// 返回:合法三段式 JWT,7 天过期,签名段 = "mock_sig"(前端不验签)
// 示例:const t = issueMockJwt("WKR", { wkr_id: "wkr_custom_007" });
三、6 角色 Mock 用户数据(DEV_TEST_USERS)¶
对齐 auth.ts §DEV_TEST_USERS:
| RoleCode | 端 | display_name | 主体 ID | 权限范围 |
|---|---|---|---|---|
| MFR-USR | mfr-portal | 測試生產商 · WEAVELY | ppl_id=ppl_test_001 |
read: qsv/cpt/mds;write: qsv |
| OL | opr-admin | 測試運營 · OL | ol_id=ol_test_001 |
read: 5全;write: cpt/ops/dis;approve: dispatch/exception |
| ML | opr-admin | 測試管理層 · 曾總 | ol_id=ml_test_001 |
read/write 5 全;approve 4 项全 |
| WKR | wkr-app | 測試師傅 · 陳師傅 | wkr_id=wkr_test_001,ppl_id=ppl_test_001(归属生产商) |
read: cpt/mds;write: cpt |
| CST | cst-app | 測試客戶 · 張先生 | cst_id=cst_test_001 |
read: qsv/cpt/mds;write: qsv(仅创建报价请求) |
| LGP-USR | lgp-portal | 測試物流 · 順豐 | lgp_id=lgp_test_001 |
read: dis/cpt;write: dis |
四、5 端本地 Mock 登录操作步骤¶
4.1 启动本地 Dev Server¶
cd dip1\frontend
# 终端 1:OPR 运营后台 + WKR H5 演示
pnpm --filter opr-admin dev # http://localhost:3000
# 终端 2:MFR 生产商门户(含开放报价独立入口 /quote/demo)
pnpm --filter mfr-portal dev # http://localhost:3001
# 终端 3:LGP 物流商门户
pnpm --filter lgp-portal dev # http://localhost:3002
# 终端 4/5(RN 真机):师傅端 / 客户端
pnpm --filter wkr-app start # Expo Go → 扫码 / i
pnpm --filter cst-app start # Expo Go → 扫码 / i
4.2 各端 Mock 登录步骤¶
| # | 端 | URL / 启动方式 | 推荐角色(高亮按钮) | 操作 |
|---|---|---|---|---|
| 1 | MFR 生产商门户 | http://localhost:3001/login | 🟢 MFR-USR 生产商 | 点击顶部「🚀 生产商(本端推荐)」→ 自动跳转 /quote;如需自定义 ppl_id,填入下方 PPL 绑定输入框后再点 Mock 按钮 |
| 2 | OPR 运营后台 | http://localhost:3000/login | 🔵 OL 运营 + 🟡 ML 管理层(2 按钮并列) | 点击「运营专员(推荐)」→ /dashboard;Sidebar 显示 display_name + 退出按钮 |
| 3 | LGP 物流商门户 | http://localhost:3002/login | 🟣 LGP-USR 物流商 | 点击「物流商(本端推荐)」→ / DIS 派单看板 |
| 4 | WKR 师傅 App | Expo Go 扫码 / i 打开 iOS Simulator |
🟠 WKR 师傅 | 启动后直接进入 Mock 登录屏 → 点击「師傅端(本端推薦)」→ 展示作业卡;顶栏显示 👤 測試師傅 · 陳師傅 · wkr_test_001 + 退出 |
| 5 | CST 客户 App | Expo Go 扫码 / i 打开 iOS Simulator |
🔵 CST 客户 | 启动后直接进入 Mock 登录屏 → 点击「客戶端(本端推薦)」→ 项目进度 + NPS 评价双 tab |
4.3 跨端角色联调¶
每端登录页均有「其他角色(跨端联调用 · 弱权限)」折叠区(Web 端用 <details>、RN 端用列表),可一键登录非本端角色。适用场景:
- 在 OPR 后台以 MFR-USR 身份查看其视角下的报价单(测试跨端权限矩阵)
- 在 WKR App 以 CST 身份查看客户视角的项目进度(省掉反复切换真机的成本)
- 注意:跨端角色登录后 API 调用仍会带上该角色的 JWT,但后端真实 403 权限拒绝需等后端就绪后才会触发——当前前端权限仅 useWhoami().user.permission 做展示用
4.4 角色切换¶
Web 端:点击页面上的「退出」按钮(OPR Sidebar 右下)→ 自动回登录页 → 选其他 Mock 角色 RN 端:点击顶栏红色「退出」按钮 → 回登录屏 → 选其他 Mock 角色
4.5 自定义主体 ID(高级)¶
需要模拟具体的师傅/客户/生产商?
方式 A:MFR 登录页 PPL 输入框
- 填入 ppl_id → 该值会同时注入 Mock JWT 和后端签发 JWT 的 claims 中
方式 B:浏览器控制台 / RN 调试(issueMockJwt 直接调用)
// Web 端 DevTools Console
import { issueMockJwt, useWhoami } from "@dip1/shared";
// 或直接访问 window(开发模式可挂载)
const token = issueMockJwt("WKR", {
wkr_id: "wkr_007_司徒超",
display_name: "自定義 · 司徒師傅", // 注:display_name 实际由 DEV_TEST_USERS 模板匹配,此处仅影响 claims,若需完全自定义 display_name 请在 overrides 中传 sub 并替换 DEV_TEST_USERS 对应项
});
// 然后 localStorage.setItem("dip1.token", token)
// localStorage.setItem("dip1.whoami", JSON.stringify(parsedUser))
location.reload();
五、Mock 数据切换机制详解¶
5.1 三层数据流¶
┌──────────────────────────────────────┐
│ ① issueMockJwt("WKR", overrides?) │ 纯前端,0 依赖
│ → header.payload.mock_sig │
└──────────────┬───────────────────────┘
▼
┌──────────────────────────────────────┐
│ ② WhoamiProvider.login(token, app) │ Web 端写 localStorage
│ → decodeJwtPayload → parseWhoami… │ RN 端写内存 useState
│ → user = DEV_TEST_USERS[role] 补齐 │ display_name / permission
└──────────────┬───────────────────────┘
▼
┌──────────────────────────────────────┐
│ ③ Dip1ApiClient (shared/client.ts) │ getToken() 自动读
│ → Authorization: Bearer <token> │ 调用 /api/v2/* 时自动注入
└──────────────────────────────────────┘
5.2 关键不变式(合规性)¶
- JWT 格式合法:严格三段式
header.payload.signature,Base64URL 编码 + 去 padding - 不改变 parseWhoamiFromJwt 的原有逻辑:display_name 与 permission 仍由 DEV_TEST_USERS[role] 补齐,仅 payload claims(sub/wkr_id/cst_id/ppl_id/lgp_id/ol_id)支持 overrides
- btoa polyfill:当全局
btoa不可用时(SSR / 老版 RN hermes),回退到fallbackBtoa手写 charCode 映射,保证 ASCII 范围内字符编码正确 - 7 天过期:
exp = now + 7*24*3600,本地测试无需频繁重登 - 0 新增 npm 依赖:issueMockJwt 全程使用 JS 原生 API,符合 MSC-FE 红线
5.3 与后端 /api/v2/dev/token 的关系¶
| 维度 | issueMockJwt(纯前端) | 后端 POST /api/v2/dev/token |
|---|---|---|
| 启动条件 | 无需任何后端进程 | 需后端运行在 localhost:9100 |
| 签发方 | 浏览器 / RN JS 引擎 | FastAPI 后端(dip1/backend) |
| 签名 | mock_sig 固定占位符 |
HS256 实际签名(JWT secret) |
| 后端验签 | ❌ 后端会拒绝(JWT signature invalid) | ✅ 通过 |
| 前端展示 | ✅ WhoamiProvider 100% 正常 | ✅ 100% 正常 |
| API 调用真实后端 | ❌ 会被 401/403 拒绝 | ✅ 正常 |
| 适用阶段 | WP-04/05/07 UI 开发 + 纯前端交互测试 | 前后端联调 + 端到端测试 |
结论:Mock JWT 仅适用于纯前端阶段。一旦后端接口就绪,切换为方式 2(调用 POST /api/v2/dev/token)或生产 OIDC 即可。
六、验证结果¶
6.1 Typecheck¶
| 包/应用 | 结果 | 耗时 |
|---|---|---|
packages/shared (pnpm --filter @dip1/shared typecheck) |
✅ 0 错误 | - |
apps/mfr-portal (pnpm --filter mfr-portal typecheck) |
✅ 0 错误 | - |
apps/opr-admin (pnpm --filter opr-admin typecheck) |
✅ 0 错误 | - |
apps/lgp-portal (pnpm --filter lgp-portal typecheck) |
✅ 0 错误 | - |
| apps/wkr-app / apps/cst-app | ⏭ 跳过(Expo tsc 需独立 expo start 环境,不在本地 Web tsc scope;WP-08 已通过同样 Expo tsc 验证) |
- |
6.2 Dev Server 冒烟(登录页加载)¶
| 端 | 登录页 URL | 状态码 | Content.Length |
|---|---|---|---|
| MFR | http://localhost:3001/login | 200 | > 0 |
| OPR | http://localhost:3000/login | 200 | > 0 |
| LGP | http://localhost:3002/login | 200 | > 0 |
6.3 Mock 按钮纯 JS 可执行性验证¶
由于 Mock 登录按钮的 onClick 逻辑仅调用 issueMockJwt(role) → login(token, app) → router.push(...),全部是内存态 + 纯 JS 操作,不涉及任何网络请求,因此在 typecheck 通过 + 组件成功 render 的前提下,逻辑可执行性已由静态类型保证。
七、关键设计决策(D1~D4)¶
| # | 决策点 | 选择 | 理由 |
|---|---|---|---|
| D1 | JWT 签名段策略 | 固定占位符 "mock_sig",不做实际 HS256 签名 | parseWhoamiFromJwt 完全不验签,0 额外成本;避免引入 crypto-js/等依赖(违反 0 新增 npm 依赖红线) |
| D2 | display_name 来源 | 仍由 DEV_TEST_USERS[role] 补齐,不从 overrides.display_name 取 | 与 WP-06 原有 WhoamiProvider 语义保持一致,减少变更面;如确实需要自定义 display_name,可在 overrides 中传 DEV_TEST_USERS 未定义的 sub → 自动 fallback 到 payload.sub 作 display_name |
| D3 | RN 端持久化 | 保持 WP-06 决策:内存态(useState),App 重启需重登 | 引入 @react-native-async-storage 违反 0 新增依赖红线;开发期重登成本可接受(一键 Mock 按钮 1 秒即达) |
| D4 | 跨端角色折叠策略 | Web 端用原生 <details> + <summary>,RN 端直接平铺 5 按钮网格 |
Web 端原生 details 零依赖、零交互成本;RN 屏幕空间充足,无需折叠;跨端角色使用频率远低于本端推荐角色 |
八、阻塞与解决(B1~B1)¶
| # | 阻塞 | 根因 | 解法 |
|---|---|---|---|
| B1 | RN Expo gap: 6 StyleSheet 警告? |
旧版 RN 不支持 gap |
实际检查 Expo SDK 51 RN 0.74+ 已全面支持 View style gap(含 Flex + Wrap),WKR/CST App 正常无警告;如确遇降级,改 margin: 3 即可 |
(本次交付仅 1 项潜在阻塞,无实质性阻断)
九、后续 TODO(供决策 · 非自动执行)¶
| # | 事项 | 触发条件 | 建议执行人 |
|---|---|---|---|
| T1 | 接入后端 /api/v2/bff/whoami 接口 |
后端接口就绪 + 用户明确指令 | WDE |
| T2 | RN 端接入 AsyncStorage 持久化 | 用户评估后决定引入 @react-native-async-storage | WDE |
| T3 | 启动 P1 WP-04(CPT 工单详情页 + 8 阶段时间线) | 后端 CPT2 接口就绪 + 用户明确指令 | WDE |
| T4 | 启动 P1 WP-05(QSV 报价全流程 + PDF 生成) | 后端 QSV 接口就绪 + 用户明确指令 | WDE |
| T5 | 启动 P1 WP-07(MDS 四库管理后台) | 后端 MDS 接口就绪 + 用户明确指令 | WDE |
| T6 | 接入 Keycloak 25 OIDC 授权码流程(V2) | 生产环境部署 + 用户明确指令 | WDE + OCM |
| T7 | Mock JWT 后端验签开关 | 需要同时支持前端 Mock + 真实后端调用场景 | 可在后端增加 dev.mock_jwt_bypass=true 环境变量,签名段为 "mock_sig" 时跳过验签(需安全评审) |
十、参考文档¶
- 上游 DTL:
- L-20260825-04(WP-06 收尾 5 端 Provider)
- L-20260825-01(5 端测试用户手册 JWT 清单)
- 关键代码:
- auth.ts(DEV_TEST_USERS + issueMockJwt + parseWhoamiFromJwt)
- whoami-context.tsx(Web 端 WhoamiProvider)
- whoami-context.rn.tsx(RN 端 WhoamiProvider)
- 登录页:MFR / OPR / LGP / WKR / CST
十一、修订记录¶
| 版本 | 日期 | 修订人 | 修订内容 |
|---|---|---|---|
| V1.0 | 2026-08-25 | DT | 初始版本:后端未就绪前 Mock 策略梳理交付(issueMockJwt + 5 端一键 Mock 登录 + 6 角色模板 + 4 设计决策 + 1 阻塞) |
本日志为「后端未就绪前 Mock 策略」最终交付。所有改造均为纯前端,0 新增 npm 依赖,未 touch Gitee / 生产 ECS / ocm/(三 AI 合规边界)。后续 T1~T7 需后端接口就绪或用户明确指令后执行。