跳转至

DTL · L-20260825-05

文档编号:DOC-A04-DTL / L-20260825-05 维护人:听写 DT(DOC-A01 PMG V8.3 §人机协作模型) 关联 DTLL-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 关键不变式(合规性)

  1. JWT 格式合法:严格三段式 header.payload.signature,Base64URL 编码 + 去 padding
  2. 不改变 parseWhoamiFromJwt 的原有逻辑:display_name 与 permission 仍由 DEV_TEST_USERS[role] 补齐,仅 payload claims(sub/wkr_id/cst_id/ppl_id/lgp_id/ol_id)支持 overrides
  3. btoa polyfill:当全局 btoa 不可用时(SSR / 老版 RN hermes),回退到 fallbackBtoa 手写 charCode 映射,保证 ASCII 范围内字符编码正确
  4. 7 天过期exp = now + 7*24*3600,本地测试无需频繁重登
  5. 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" 时跳过验签(需安全评审)

十、参考文档


十一、修订记录

版本 日期 修订人 修订内容
V1.0 2026-08-25 DT 初始版本:后端未就绪前 Mock 策略梳理交付(issueMockJwt + 5 端一键 Mock 登录 + 6 角色模板 + 4 设计决策 + 1 阻塞)

本日志为「后端未就绪前 Mock 策略」最终交付。所有改造均为纯前端,0 新增 npm 依赖,未 touch Gitee / 生产 ECS / ocm/(三 AI 合规边界)。后续 T1~T7 需后端接口就绪或用户明确指令后执行。