@kesi/mock
v1.0.1
Published
KESI 前端内存 mock —— URL 驱动的整站演示数据 + axios/WebSocket 传输层拦截
Readme
@kesi/mock
KESI 前端内存 mock —— 单一 JSON 数据源 + axios / WebSocket 传输层拦截。 不依赖真实后端,业务代码零改动即可跑通全平台演示。
从 packages/kesi/src/lib/kesi/mock/ 抽出为独立工作区库包,应用侧只剩接线。
特性
- 一个场景一个数据包:演示数据是
data/下的 JSON 文件(唯一真源,可手改)。 各src/data/*.ts只剩规则(handler / 资源声明),不含任何记录数组 —— 两个场景共用同一套规则,只是数据不同。 - 运行时动态获取:
src/loader.ts先fetch(真实 HTTP),失败回退动态import()(独立懒加载 chunk)—— 数据不进任何 JS 启动包,也不内联进产物。 - URL 前缀即开关:
/_m_<name>/启用,无前缀即真实模式(无默认值)。<name>是数据包别名(building→ 楼宇包)时装载该包全部数据集; 是数据集名(iot/ai…)时用默认包,只装载 base + 该数据集;all装载默认包全部数据集。 - 按数据集分片:JSON 里每个集合按数据集分片,因此
_m_iot只装 base + iot, 语义与旧版逐数据集注册逐字节一致。 - 只拦网络层:复用
@kesi/client的全部真实逻辑(createAPI / convert_* / model / auth),只替换传输。 - 契约对齐:查询引擎(filter 操作符 / sort / skip / limit / count)与真实后端语义一致; 记录在装载时做结构 + 契约校验兜底(见下)。
- 可见的兜底:未覆盖端点仍返回 200(不让页面白屏),但带
x-mock-demo提示头 + 一次性告警。 - WS 桥:接管
window.WebSocket,订阅转事件总线,消除真实/ws重连刷屏。
可用数据包
| 别名 | 文件 | 场景 | 数据集 | 规模 |
|---|---|---|---|---|
| factory(默认) | data/mock-data.json | 智慧工厂(中文) | base, iot, business, video, apps, ai, system | 79 集合 / 209 条 |
| building | data/mock-data-building.json | 智慧楼宇(中文) | base, building | 81 集合 / 214 条 |
| en / factory-en | data/mock-data-en.json | 智慧工厂(English) | base, iot, business, video, apps, ai, system | 79 集合 / 209 条 |
| building-en | data/mock-data-building-en.json | 智慧楼宇(English) | base, building | 81 集合 / 214 条 |
http://localhost:5173/_m_all/ # 工厂(默认包)全部数据集
http://localhost:5173/_m_iot/ # 默认包:base + 物联
http://localhost:5173/_m_building/ # 智慧楼宇(中文)
http://localhost:5173/_m_en/ # 智慧工厂(English)
http://localhost:5173/_m_building-en/ # 智慧楼宇(English)工厂场景内容:7 类设备模型(空压机 / 注塑机 / CNC / 燃气锅炉 / 循环水泵 / 变频器 / 环境传感器)、 15 台设备、报警、6 张工厂管理表、流程 / 报表 / 数据集。
楼宇场景内容:9 类机电设备模型(冷水机组 / 空调机组 AHU / 新风机组 / 电梯 / 照明回路 / 给排水泵 / 智能电表 / 门禁闸机 / 环境传感器)、19 台设备、7 条告警 + 4 条规则告警、 6 张运维表(巡检 / 维保工单 / 分项能耗 / 备件 / 环境监测 / 访客登记)、楼宇流程 / 报表 / 数据集、 监控点位与 AI 助手命名。
平台脚手架(登录 / 设置 / 用户 / 消息、系统备份、模型供应商等)各包一致。
英文包的翻译约定(重要)
英文包把展示文案(设备名、字段标题、描述、消息、区域、人名、公司名…)全部英文化, 但刻意保留下面这些字段的中文原值:
| 字段 | 为什么保留 |
|---|---|
| warning/warning[*].level / .status / .processed | src/schemas/warning.schema.ts 的筛选枚举就是 ['低','中','高'] / ['未确认','已确认'] / ['未处理','已处理'];ClearAlarmDialog 等直接以 filter.processed = '已处理' 查询 |
| core/t/schema[*].warning.rules[*].level | DeviceViewDialog / AlarmRulesTab 用 level === '高' 着色 |
| core/message[*].messageType / .optType | 应用把它们当 i18n key(en.json 里 系统→System、通知→Notification) |
本应用的约定是「中文值当 i18n key / 筛选枚举」,所以保留中文反而得到英文界面 + 可用筛选;
若把这些也翻成英文,界面同样是英文,但筛选、统计与告警着色会全部失配。
英文包与中文包的结构与记录 ID 完全一致(npm run smoke:bundles 会校验),
以后往中文包加设备时记得同步英文包。
新增第三个场景:把 data/mock-data-<name>.json 放好,并在 src/loader.ts 的
MOCK_BUNDLES 与 BUNDLE_IMPORTERS 各登记一行(后者必须是字面量 import,否则打包器
无法切分懒加载 chunk)。
数据放在哪、怎么被读到
data/mock-data*.json ← 4 个数据包(唯一真源;中英各 2 个,142~160KB)
│
├─ 1) fetch 调用处传入的地址 ← installMockIfNeeded({ dataUrl })
│ 或 setMockDataUrl(url) / window.__MOCK_DATA_URL__
├─ 2) fetch `${BASE_URL}mock/<包文件名>` ← 推荐:真实 HTTP
│ (宿主 `npm run data:copy -- <静态目录>`,如 -- ../kesi/public,会复制全部包)
├─ 3) 动态 import('../data/<包文件名>') ← 兜底:打包器切成独立懒加载 chunk
└─ 4) provideMockData(json) ← Node 脚本 / 宿主接口拉取后注入
↓
src/loader.ts → 按数据集分片 seedCollection(结构 + 契约校验)
↓
内存集合 + 查询引擎(src/store.ts)fetch 候选都失败时不静默:打印明确错误 + 修复指引,并以空集合继续
(请求走演示兜底 200 + x-mock-demo 头,缺口可见但不白屏)。
显式指定的地址(第 1 条)失败时另有一条点名告警 —— 否则公网地址写错 / 被 CORS 拦下时,
会静默跑着内置数据,排查毫无线索。
用公网地址托管数据包
dataUrl 接受任意绝对 http(s) 地址(第 1 条候选本来就支持跨域),例如 CDN / 对象存储 /
raw.githubusercontent.com:
installMockIfNeeded({ dataUrl: 'https://cdn.example.com/kesi/mock-data-building.json' });三条硬性前提:
- CORS:响应必须带
Access-Control-Allow-Origin(允许本站源或*)。 我们只发简单 GET(无自定义头)→ 不触发预检,不需要Access-Control-Allow-Headers。 - HTTPS:页面是 https 时地址也必须是 https,否则被浏览器的混合内容策略拦截。
- 内容:GET 直接返回
kesi-mock-data格式的 JSON(format/version/datasets/collections)—— 把data/下的文件原样上传即可,不需要任何加工。
注意:公网地址只是换数据来源,_m_<别名> 选包 / 选数据集的逻辑不变(远端包里的
datasets 决定可选项)。当前不接受自定义请求头 —— 需要鉴权头的私有地址请用
provideMockData(json) 自己拉取后注入。
用法(消费方)
// main.tsx —— 必须排在 App 之前(模块求值期副作用:URL 含 /_m_ 时同步接管传输层)
import { installMockIfNeeded } from '@kesi/mock/bootstrap';
import { initKesiConfig } from '@/lib/kesi/config';
import App from './App';
// 数据地址可选:不传则自动探测(BASE_URL 静态目录 → 动态 import 兜底)
installMockIfNeeded().finally(() => {
initKesiConfig();
render(<App />);
});
// 指定数据地址(三种等价写法)
installMockIfNeeded({ dataUrl: '/static/mock-data.json' });
installMockIfNeeded('/static/mock-data.json'); // 字符串简写
// 或装载前调用底层 API(手动模式也适用):
// import { setMockDataUrl } from '@kesi/mock';
// setMockDataUrl('/static/mock-data.json');dataUrl 会在装载之前登记为最高优先级的 fetch 地址;真实模式(URL 无 /_m_)下即使传了
也不会发起任何请求。
想让数据走真实 fetch(而不是回退 chunk),把 JSON 放进宿主静态目录:
cd packages/mock
npm run data:copy -- ../kesi/public
# → packages/kesi/public/mock/mock-data.json
# packages/kesi/public/mock/mock-data-building.json参考实现:packages/kesi/src/main.tsx。
什么不该进本包:应用的认证/登录态逻辑(如 packages/kesi 的 setupAuthInterceptor,
处理 401/403 后清登录态与跳转)留在应用侧。判据是「这件事属于谁」——
mock 的启用判定与免登录属 mock,应用登录态属应用。
三条硬约束(改错会静默失效或让数据回到启动包)
- axios / @kesi/client / react / dayjs 是 peerDependency,构建时 external。
mock 靠 patch 消费方的 axios 实例拦截请求;一旦把 axios 打进本包,补丁作用在另一份副本上,
拦截会静默失效(请求照常打到真实后端且无任何报错)。
@kesi/client同理——它的 config 是 模块级单例,bootstrap 的自动登录要setConfig写它,副本化后写不到应用那份。消费方还需把 axios 放进resolve.dedupe。 - barrel 不得静态再导出
./data/**。data/index.ts静态 import 了全部数据集规则文件,被 barrel 再导出会把整张规则图拉进消费方启动包。 数据入口一律深路径:await import('@kesi/mock/data')、@kesi/mock/data/base。 (src/loader.ts在src/顶层、不 import 任何数据集文件,所以可以从 barrel 安全导出。) - 消费方需
optimizeDeps.exclude: ['@kesi/mock']。 预打包会把本包摊平成单文件,loader.ts里import('../data/mock-data*.json')的懒加载被拉直。
另注:不要把数据改回 new URL('../data/mock-data.json', import.meta.url)。
Vite 在 library 模式下会把这类资源内联成 base64 data URL 写进 JS(实测 loader.js 从 9KB 涨到 198KB),
等于把数据塞回产物里。fetch + 动态 import() 的组合才是「数据不进 JS」。
新增数据包时,BUNDLE_IMPORTERS 里必须是字面量 import(变量路径打包器无法切分 chunk)。
命令
npm run build # 构建 dist(vite preserveModules + tsc 声明文件)
npm run type-check
npm run smoke # 传输层 21 项断言
npm run smoke:client # 真实 @kesi/client 整链路 9 项断言
npm run smoke:bootstrap # bootstrap 数据地址参数 7 项断言(含真实模式不发请求)
npm run smoke:bundles # 数据包 34 项断言(别名 / 场景不串味 / 英文包 / 结构镜像 / 产物兜底)
npm run check # 数据校验:全部数据包(结构 + 契约);CI:有 error 级违规即非零退出
npm run check -- data/mock-data-building.json # 只校验指定包
npm run data:copy -- <静态目录> # 把全部 mock-data*.json 复制到宿主静态目录(走真实 fetch)本包不依赖应用,scripts/ 下的脚本可独立运行(Node 24 原生类型擦除,直接跑 .ts)。
应用侧 npm run mock:smoke / mock:check 是转发到这里。
改了 src/ 之后必须重新 npm run build —— 应用消费的是 dist 产物。
目录
data/ # ★ 数据包(唯一真源,集合 → 数据集分片 → 记录)
├── mock-data.json # 智慧工厂(79 集合 / 209 条)
├── mock-data-building.json # 智慧楼宇(81 集合 / 214 条)
├── mock-data-en.json # 智慧工厂 English
└── mock-data-building-en.json # 智慧楼宇 English
src/
├── index.ts # 纯 barrel(无副作用):传输层 + 规则 + 契约 + 事件 + stubs
├── bootstrap.ts # ★ 启动引导:模块求值期装传输层 + 渲染前装数据/免登录(入口 @kesi/mock/bootstrap)
├── url.ts # mock name 解析(零依赖,bootstrap 与 data 共用)
├── loader.ts # ★ 数据装载:fetch / 动态 import / 注入 → 按数据集分片注册 + 结构校验
├── mock-config.ts # 运行时配置(log / defaultDelay);router 与 loader 的公共上游
├── install.ts # installMockTransport(同步)/ installMockData(异步)/ installMockAdapter
├── adapter.ts # axios adapter:URL 归一 → router;4xx 按 AxiosError 抛;__MOCK_REQ_LOG__
├── router.ts # 请求入口 await 数据就绪;规则匹配 → 集合 CRUD → 演示兜底
├── store.ts # 内存集合 + 查询引擎(不裁剪字段投影)+ 装载时契约校验
├── contracts.ts # 种子契约表(字段 / 类型 / 取值范围)
├── websocket.ts # window.WebSocket 桥
├── push.ts # 模拟推送定时器(message 20s / logs 8s / historycomputed 2s)
├── events.ts # 事件总线 + 响应式值仓库
├── stubs.tsx # useWS / useTag / useTableData / useServerTime(手动模式覆盖用)
└── data/ # 各数据集:只有规则,没有记录
├── index.ts # 注册表 + registerMockByName + 数据装载 API 再导出
├── base.ts # 登录链路 / 设置 / 静态资源与动态模式声明(+ mockUser 常量)
├── messages.ts iot.ts business.ts video.ts apps.ts ai.ts system.ts数据校验(结构 + 契约)
两层防御,由 npm run check 对每个数据包跑一遍:
- JSON 结构校验(
loader.validateMockDataBundle):format/version/datasets/collections[*].parts[*]必须是数组 —— 手改 JSON 写错形状会立刻报错,不会静默进内存。 - 契约校验(
contracts.ts):把「数据字段必须与前端类型一致」从口头约定变成可执行校验。bugs/下 BUG-033/037/038/039/044/048/050 属同一族(字段名或结构错配 → 列表空白 / 按钮禁用 / 关联数恒 0 / 详情弹窗空壳),现在会被拦下。
新增或修改数据时,若该集合在真实后端有稳定 shape,请顺手在契约表登记。
跨场景的一致性由 npm run smoke:bundles 兜底:各包子集互不串味,且英文包与中文包结构 / ID 完全镜像。
已知残留
见 packages/kesi/docs/mock-api-mode.md §十一(生产构建下 core/setting/part 有 1 次真实请求,
存量问题,怀疑 @kesi/client 产物内联了自己的 axios 副本)。
