4. 农业监控数据模拟与Mock服务搭建

在农业远程监控Web看板的开发前期,真实大棚的传感器数据往往拿不到:硬件还在安装、网关尚未联调、或者现场只有一两个棚能出数据。如果前端和看板逻辑一直等真实数据,开发进度就会被卡住。本节要解决的就是这个问题——用Mock.js和json-server搭建一套“像真的”模拟数据服务,让看板在没有真实硬件的情况下也能完整跑通分页、时间筛选和异常告警展示。

先明确本节要模拟的四类核心数据对象。第一类是环境数据:空气温度(℃)、空气湿度(%RH)、土壤EC值(mS/cm)、光照强度(lux),它们按时间序列产生,是看板曲线和仪表盘的主要来源。第二类是设备状态:传感器编号、所属大棚、在线/离线状态、最后上报时间、电量。第三类是分页与筛选参数:page、pageSize、startTime、endTime、greenhouseId。第四类是异常值:用于验证看板告警颜色、阈值判断和异常标记是否生效。这四类对象决定了接口的字段结构和返回格式,先定字段再写服务,能避免后期反复改前端。

用Mock.js生成结构化随机数据

Mock.js适合在纯前端阶段快速造数据,核心是利用它的模板语法生成符合业务范围的随机值。下面这段示例定义了一个生成单条环境记录的模板,参数为合理假设的教学示例,实际项目可按传感器量程调整:

// mock/env.js
const Mock = require('mockjs');
const Random = Mock.Random;

function makeEnvRecord(greenhouseId, time) {
  return {
    id: Random.guid(),
    greenhouseId,
    timestamp: time,
    temperature: +Random.float(18, 32, 1, 1),   // ℃
    humidity: +Random.float(45, 85, 1, 1),      // %RH
    soilEc: +Random.float(0.8, 2.6, 2, 2),      // mS/cm
    light: Random.integer(8000, 65000),         // lux
    deviceOnline: Random.boolean(9, 1, true)    // 90%在线
  };
}

这里有两个细节值得注意。一是数值范围要贴近真实农业场景:番茄大棚白天温度常在20~30℃,湿度60%~80%,土壤EC在1.0~2.5 mS/cm之间比较常见,光照晴天可达数万lux。二是deviceOnline用Random.boolean(9,1,true)让在线概率约为90%,这样看板的在线率统计才有波动感,而不是永远100%。生成时间序列时,用循环按固定间隔(如每5分钟一条)推进时间戳,就能得到一条连续的曲线数据。

用json-server提供可分页、可筛选的REST接口

Mock.js擅长造数据,但分页和按时间范围筛选如果全靠手写路由会比较繁琐。json-server的优势在于:给定一个JSON数据文件,它自动提供GET /envRecords接口,并原生支持_page、_limit分页参数,以及字段等值筛选。对于时间范围这种“大于等于、小于等于”的条件,可以通过自定义中间件或路由实现。下面是一个可运行的配置示例:

// db.json 结构(节选)
{
  "envRecords": [
    { "id": "e1", "greenhouseId": "GH-01",
      "timestamp": "2024-06-01T08:00:00",
      "temperature": 24.6, "humidity": 68.2,
      "soilEc": 1.42, "light": 32000, "deviceOnline": true }
  ],
  "devices": [
    { "id": "D-01", "greenhouseId": "GH-01",
      "type": "温湿度传感器", "online": true,
      "lastReport": "2024-06-01T08:00:00", "battery": 87 }
  ]
}

// server.js
const jsonServer = require('json-server');
const server = jsonServer.create();
const router = jsonServer.router('db.json');
const middlewares = jsonServer.defaults();

server.use(middlewares);
server.get('/envRecords', (req, res) => {
  const { startTime, endTime, greenhouseId } = req.query;
  let data = router.db.get('envRecords').value();
  if (greenhouseId) data = data.filter(r => r.greenhouseId === greenhouseId);
  if (startTime) data = data.filter(r => r.timestamp >= startTime);
  if (endTime)   data = data.filter(r => r.timestamp <= endTime);
  const page = +req.query._page || 1;
  const limit = +req.query._limit || 20;
  res.json({
    total: data.length,
    page, pageSize: limit,
    list: data.slice((page - 1) * limit, page * limit)
  });
});
server.use(router);
server.listen(3001, () => console.log('Mock服务运行在 3001'));

启动后,前端请求GET /envRecords?greenhouseId=GH-01&startTime=2024-06-01T00:00:00&endTime=2024-06-01T12:00:00&_page=1&_limit=20,就能拿到带total、page、list的标准分页响应。看板的分页组件直接读total算总页数,时间选择器把起止时间拼进查询串,筛选逻辑就闭环了。注意时间字符串统一用ISO格式,保证字符串比较与时间先后一致,这是筛选正确的前提。

异常值注入:让告警逻辑真正被验证

只造正常数据,看板的红色告警、阈值判断永远触发不了。异常值注入就是在生成数据时按一定比例写入越界值,例如温度超过35℃、湿度低于30%、土壤EC高于3.0 mS/cm、光照低于2000 lux,或把deviceOnline置为false。建议用“异常标记”字段区分,而不是让异常值混在正常数据里无法追溯:

function injectAnomaly(record, rate = 0.08) {
  if (Math.random() < rate) {
    const type = Random.pick(['tempHigh', 'humLow', 'ecHigh', 'lightLow', 'offline']);
    if (type === 'tempHigh') record.temperature = +Random.float(35, 42, 1, 1);
    if (type === 'humLow')   record.humidity = +Random.float(15, 29, 1, 1);
    if (type === 'ecHigh')   record.soilEc = +Random.float(3.1, 4.5, 2, 2);
    if (type === 'lightLow') record.light = Random.integer(200, 1900);
    if (type === 'offline')  record.deviceOnline = false;
    record.anomalyType = type;
  }
  return record;
}

注入比例建议控制在5%~10%,既能稳定触发告警,又不至于满屏红色失去真实感。看板侧据此验证:温度超过35℃是否变红、离线设备是否灰显、异常记录是否出现在告警列表。这样前端告警逻辑在真实硬件到位前就完成了验证。

农业监控Mock数据服务结构 数据生成层 Mock.js 模板 温湿度 / 土壤EC / 光照 设备在线状态 时间序列(5分钟间隔) 异常值注入(5%~10%) json-server 服务层 GET /envRecords _page / _limit 分页 startTime / endTime 筛选 greenhouseId 过滤 GET /devices 设备状态 Web看板消费层 曲线图 / 仪表盘 分页表格 时间范围选择器 在线率统计 告警列表 / 红色标记 查询参数:page / pageSize / 时间范围 / 大棚ID 统一响应结构 { total: 240, page: 1, pageSize: 20, list: [ { temperature, humidity, soilEc, light, deviceOnline, anomalyType } ] }

接口约定与联调要点

Mock服务不是随便返回一堆数据就完事,它要和真实后端保持同一套接口契约,否则将来切换真实接口时前端要返工。建议在项目里维护一份接口约定表,明确字段名、类型、单位、分页参数名和响应外层结构。例如温度字段统一叫temperature、单位℃,时间统一ISO字符串,分页统一用total/page/pageSize/list。前端把请求地址抽成配置项,开发阶段指向http://localhost:3001,上线时只改一个baseURL即可。

联调时重点观察三件事:一是翻页后total是否稳定、最后一页条数是否正确;二是时间范围边界是否包含端点,跨天查询是否漏数据;三是异常记录是否在看板上被正确标红并进入告警列表。如果发现筛选后总数不对,优先检查时间字符串格式是否统一、比较时是否混用了本地时间与UTC。把这些验证做扎实,Mock服务就真正起到了“替身演员”的作用,而不是一个只能看不能用的摆设。

建议把生成脚本写成可重复执行的形式:每次启动前清空并重新生成db.json,保证数据量、异常比例和时间跨度可控。需要复现某个告警场景时,固定随机种子或直接手写几条异常记录,比反复刷新碰运气更可靠。