ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

燃气入户安检管理系统与设备巡检APP:离线同步和隐患闭环设计

燃气入户安检管理系统与设备巡检APP:离线同步和隐患闭环设计

燃气入户安检管理系统和燃气设备巡检系统APP,这两个词看起来像产品说明书里的名词,实际落地的时候完全不是一回事。我做过几年燃气行业的移动作业系统,从最早的纸质安检单拍照回传,到后来做完整的入户安检APP、管网巡检APP,中间踩过的坑比写过的代码还多。这套东西的本质,是把安检员在用户厨房里做的每一件事、巡检员在阀门井边看的每一个点位,变成可追溯、可统计、可闭环的数据。它解决的不是“有没有系统”的问题,而是“安检到底做没做、做得规不规范、隐患到底整没整改”这三个要命的问题。适合谁看?如果你是燃气公司的信息化负责人、外包实施团队的开发、或者是刚接手这类项目的产品经理,这篇东西能帮你少走至少半年的弯路。下面我按实际项目的推进顺序,把设计思路、核心模块、技术难点和现场踩坑一条条拆开讲。

1. 业务链条先捋顺,别急着写代码

1.1 入户安检到底在检什么

很多人一上来就画ER图,结果字段设计得一塌糊涂,根本原因是没有跟着安检员跑过一天。民用户入户安检的核心检查对象就那么几类:户内管道及接口、燃气表、表前表后阀门、连接软管、灶具、热水器/壁挂炉、排烟条件、通风条件、燃气报警器与切断装置。每一项都有对应的判定标准,比如软管要看是否老化龟裂、是否超长、是否穿墙、是否使用非专用软管;灶具要看是否有熄火保护、是否超期服役;热水器要看烟道是否完好、是否安装在浴室内。这些检查项不是随便列几条就完事,它直接决定了现场表单的结构和后续隐患分级的口径。

我在第一个项目里犯过的错,是把所有检查项做成一张大表单,几十个字段平铺,安检员在厨房里蹲着滑手机,滑到第八屏就烦了,开始瞎点。后来改成“分组折叠+必填项前置+异常项才展开详情”的结构,单户录入时间从平均11分钟降到6分钟左右。这个改动的背后逻辑很简单:现场作业的人注意力是稀缺资源,界面上每多一个无关字段,数据质量就往下掉一点。

还有一点,安检项要区分“观察项”和“判定项”。观察项是记录性质,比如“管道走向”“表具位置”;判定项是有明确结论的,比如“软管是否老化:是/否”。判定项才是隐患生成的触发器,观察项只是辅助溯源。这个区分如果不做,后面隐患统计会乱成一锅粥。

1.2 从纸质台账到移动巡检,中间隔着什么

纸质台账时代,安检员填单、用户签字、回公司交单、内勤录入Excel、月底汇总。这条链路上有三个致命断点:签字可以代签、隐患整改没人跟踪、数据滞后一个月。移动巡检系统要补的就是这三块。但很多团队只做到了“把纸单变成电子单”,也就是把表单搬上手机,签个字拍个照,这其实只是把纸质流程电子化,价值有限。

真正的跃迁在于流程重构。安检任务由系统按片区、按楼栋、按上次安检时间自动生成并派发;安检员到场后扫码或定位签到;检查项逐条判定,异常项强制拍照;隐患自动进入整改池并按等级分配给对应责任人;整改完成后由安检员或班组长复核关闭。整条链路形成闭环,每个环节都有时间戳和操作人。

这里面有个容易被忽略的设计点:任务生成策略。是按“到期前30天生成”还是“按季度批量生成”?我的经验是按“上次安检日期+周期-提前量”滚动生成,提前量设置为15到30天,既保证不逾期,又不至于一次生成太多任务把APP列表撑爆。滚动生成的实现也不复杂,一个定时任务每天凌晨跑一遍,扫描满足条件的用户档案,插入任务表并做唯一性约束(同一用户同一周期只能有一条任务),避免重复派单。

1.3 角色划分决定了权限模型

这套系统里至少有五类角色:一线安检员、班组长/网格长、安全管理员、内勤/客服、管理层。安检员只能看自己名下的任务;班组长要看整个网格的任务和隐患;安全管理员关注重大隐患和整改率;内勤负责工单回访和用户预约;管理层只看报表。权限模型如果一开始不做细,后面加字段级权限会很痛苦。

我比较推荐的做法是基于RBAC做角色,再叠加数据范围(data scope)控制。角色决定能访问哪些功能菜单,数据范围决定能看哪些数据。数据范围常用的几种:本人、本班组、本网格、全部。实现上就是在查询条件里动态拼接组织架构路径,比如用类似org_path like '/1001/1003/%'的方式做子树查询,比递归查子部门简单得多,性能也稳。

提示:不要在代码里硬编码角色判断,比如if (role == 'admin')。这种写法在角色扩展到七八个之后会变成灾难,改用权限码加注解拦截,后期加角色只改配置。

2. 整体架构与技术选型,钱要花在刀刃上

2.1 端-云-后台的三层结构

这套系统的典型形态是:移动端APP(Android为主,部分场景需要iOS)、服务端API、Web管理后台,外围还有短信/推送通道、对象存储、地图服务。移动端负责采集,服务端负责业务逻辑和数据一致性,后台负责配置、统计和监管上报。

服务端我一般建议单体优先,别一上来就微服务。燃气公司的用户量级通常是几万到几十万户,日活安检员几百到几千人,峰值QPS撑死几百,一个做了良好分层的单体应用完全扛得住,运维成本还低。等到确实出现模块间耦合严重、发布互相阻塞的时候再拆,那时候业务边界也清晰了。

数据库选MySQL或PostgreSQL都行,我倾向PostgreSQL,JSONB字段处理动态表单数据特别顺手,GIS相关的点位查询也有PostGIS兜底。缓存用Redis,主要放字典、会话、任务计数。对象存储放照片和签名图,千万别把图片存数据库里,这是我在早期项目里干过的蠢事,一张表三个月涨到200G,备份恢复能等到怀疑人生。

2.2 移动端为什么建议原生或混合但要“离线优先”

安检现场的网络条件比你想象的差。老小区的地下室、高层电梯厅、管道井附近,信号经常是“有格但没网”。如果APP是纯在线架构,安检员点一下转三秒圈,体验直接崩。所以移动端的第一设计原则是离线优先:所有表单数据先写本地库,同步是后台异步行为,界面上明确显示“已保存本地/已同步/同步失败”。

技术选型上,如果团队有原生开发能力,Android用Kotlin,iOS用Swift,体验最好。如果人手紧张,Flutter是个不错的折中,一套代码两端跑,本地数据库用sqflite或drift,同步逻辑自己写。不推荐纯H5套壳,弱网下的页面切换和相机调用会很难受,而且大量图片上传时WebView内存容易爆。

本地库的表设计要和云端保持结构一致,但要多几个字段:sync_status(0未同步/1已同步/2同步失败)、local_id(本地生成的UUID,作为幂等键)、updated_at(本地修改时间)。服务端接收数据时以local_id做去重,这样即使网络抖动导致重复提交,也不会产生脏数据。

2.3 核心数据模型:五张主表撑起大半业务

不管功能怎么加,这套系统的数据骨架基本就是五张核心表:用户档案表、设备表、任务表、检查记录表、隐患表。用户档案存户号、地址、联系人、联系电话、楼栋单元房号、用户类型(民用/商用)、安检周期;设备表存表具编号、表具类型、安装位置、上次检定日期;任务表存任务编号、类型(入户安检/管网巡检)、计划时间、执行人、状态;检查记录表存任务下的每一条检查项结果;隐患表存隐患等级、描述、照片、整改期限、整改状态。

这里的关键是检查记录表的设计。千万不要给每个检查项建一个字段,那是自杀式设计。正确做法是行式存储:一条记录一行,字段包括item_code(检查项编码)、item_value(判定结果)、remark(备注)、photos(照片ID列表)。这样加检查项只需要改配置表,不用改表结构,也不用发版。

表结构大概长这样:

CREATE TABLE inspection_item_record ( id BIGSERIAL PRIMARY KEY, task_id BIGINT NOT NULL, user_id BIGINT NOT NULL, item_code VARCHAR(64) NOT NULL, item_value VARCHAR(32), remark VARCHAR(512), photo_ids JSONB, create_time TIMESTAMP DEFAULT now(), local_id VARCHAR(64) UNIQUE ); CREATE INDEX idx_task_item ON inspection_item_record(task_id, item_code);

local_id加唯一索引,就是幂等同步的兜底。客户端重传时,服务端捕获唯一键冲突直接跳过,返回成功即可,比先查后插效率高,也不会有并发漏洞。

3. 核心功能模块,哪些是真需求哪些是伪需求

3.1 任务派发与排班:别做成花架子

任务派发看起来简单,实际是业务矛盾的集中地。网格化管理下,一个安检员负责几个小区,任务要按地理聚类分配,否则一天跑五个小区,路上时间比干活时间长。我在实现里加了简单的聚类逻辑:先按小区ID分组,同小区任务数量超过阈值的分配给同一人,不足阈值的做邻近合并。

排班功能要克制。很多方案里画了复杂的排班日历、拖拽调整,实际上线后没人用,因为燃气安检的计划性很强,按周派单就够。真正有用的是“今日任务”“本周任务”“逾期任务”三个视图,加上一键重新分配。别把精力浪费在日历控件上。

还有个细节:任务状态机要设计清楚。状态至少包括待执行、执行中、已完成、已逾期、已作废。状态流转必须服务端校验,比如“已完成”的任务不允许再改检查项,只能走“复核退回”流程。客户端也要做同样的限制,但客户端只是体验优化,真正的防线在服务端。

3.2 现场表单:动态表单引擎是效率关键

前面说过检查项要可配置,具体实现就是一套轻量的表单引擎。配置端定义表单结构,存成JSON,客户端拉取后动态渲染。一个检查项节点的结构大致是:

{ "code": "HOSE_AGING", "label": "连接软管是否老化龟裂", "type": "radio", "options": [{"value": "Y", "label": "是"}, {"value": "N", "label": "否"}], "required": true, "trigger": {"on": "Y", "action": "create_hazard", "level": "GENERAL"}, "photoRequired": true }

trigger字段是精髓,它把“判定结果”和“隐患生成”绑在一起。安检员点了“是”,系统自动弹出隐患等级选择和拍照要求,不用再去另一个页面手动建隐患。这个设计让隐患漏报率下降非常明显,因为安检员不用做额外的判断动作。

渲染层要注意性能。一面表单几十个字段,如果每个字段都是独立组件并且都监听全局状态,列表滑动会卡。做法是分组渲染,每组一个状态容器,组内字段用局部状态管理,提交时统一汇总。

注意:动态表单的配置版本必须锁定。任务生成时记录当时的表单版本号,安检员执行时按任务上的版本号拉取表单定义。否则配置改了一半,正在执行的安检员会看到新旧混合的字段,数据直接对不上。

3.3 隐患分级与整改闭环:整套系统的价值中枢

隐患这块我见过太多项目做成“记录一下就算了”。真正有价值的闭环是:生成隐患→定级→指派责任人→限期整改→整改反馈(带照片)→复核关闭→超期升级上报。

隐患等级一般分三级:一般隐患(现场可提醒用户自行处理,比如软管轻微老化)、较大隐患(需要限期整改,比如灶具无熄火保护)、重大隐患(需要立即处置,比如管道泄漏、私接私改)。等级不同,处理时效不同,重大隐患要求现场即刻处置并同步上报,这个上报动作要有通知触达,不能只是数据库里多一条记录。

整改闭环的难点在“谁来复核”。如果让原安检员复核,容易出现自己检自己改的情况。比较稳妥的做法是班组长复核,或者设置抽检比例,比如重大隐患100%复核,一般隐患抽检20%。复核记录要单独存表,不能覆盖原始记录,保留完整证据链。

超期升级也要做。整改期限到期未处理的,系统自动把隐患状态置为“超期”,并推送给上一级。这个定时任务的实现要注意幂等,同一条隐患只升级一次,用状态字段加日志表双重保险。

3.4 拍照取证、电子签名与离线缓存

照片是这套系统里最占资源也最容易被忽视的部分。现场照片有两个硬要求:必须带水印(时间、地点、用户户号),必须来自相机实时拍摄不能从相册选。第二点是为了防止作弊,实现上调用相机API时直接拍照,不给相册入口。

水印建议客户端合成,服务端只做校验。客户端拍照后立即在图片右下角渲染半透明文字水印,把经纬度和服务端时间写进去。服务端收到后校验时间戳与任务执行时间是否在合理区间内,经纬度是否在用户地址的电子围栏范围内。

电子签名用Canvas实现,手指轨迹记录成点数组,最后转成PNG上传。签名图不用太大,宽度800像素足够,太大了纯属浪费存储。签名要和任务绑定,一个任务一份签名,变更任务状态时签名不可修改。

离线缓存这块,核心是同步队列。客户端每次保存表单,同时往sync_queue表插一条记录,包含操作类型(新增/修改)、实体类型、实体ID、payload。同步线程按队列顺序处理,失败了标记状态并退避重试。退避策略建议指数退避,第一次10秒,第二次30秒,第三次2分钟,最多重试10次,之后转人工处理。

3.5 管网巡检:二维码与NFC打卡的取舍

除了入户安检,很多方案还要覆盖燃气管网的设备巡检,比如调压箱、阀门井、立管、引入管、阴极保护测试桩。这部分的核心诉求是“人到场”,防代检是第一位的。

打卡方式有三种主流选择:二维码贴牌、NFC标签、GPS电子围栏。二维码成本最低,但容易被拍照后在家扫;NFC防伪性最好,但手机必须支持NFC且要贴标签,成本高;GPS围栏不需要物理标签,但精度受环境影响,室内基本失效。

我的实践是组合使用:室外点位用GPS电子围栏加二维码,围栏半径设50到100米,扫码校验位置;室内或地下点位用NFC。校验逻辑写在一处,客户端传上来位置和标签码,服务端统一判定,判定失败记录异常但不直接拒绝,允许补录并标记“异地打卡待核查”,避免现场信号问题导致工作量白费。

打卡方式单点成本防作弊强度适用场景主要缺点
二维码极低中小区立管、室外表箱可拍照代扫
NFC标签中高阀门井、调压箱需手机支持NFC
GPS围栏零低到中室外开阔点位室内漂移严重
组合方案中高全场景实施复杂度上升

3.6 统计报表与监管上报

管理层的需求往往很集中:安检覆盖率、隐患发现率、整改及时率、超期率、复检合格率。这几个指标要能从底层数据直接算出来,而不是靠人工Excel汇总。指标口径一定要在项目启动时和业务方敲死,否则后期扯皮。

举个例子,“安检覆盖率”有三种口径:按计划任务完成率、按实际户数覆盖数、按有效安检数(排除到访不遇)。三个数字可能差出十几个百分点。我一般会同时出三个:计划完成率给班组长看,有效覆盖率给管理层看,到访不遇单独列一个待回访清单给内勤。

报表实现上,实时查询和预计算要分开。日活级别的列表查询走实时,月度汇总走定时任务预计算落表。别拿一条大SQL去扫百万级明细表,那会把数据库拖死。预计算的表按天或按月打平,字段包括组织、日期、任务数、完成数、隐患数、整改数,查询时直接聚合,秒级响应。

4. 三个最容易被低估的技术难点

4.1 弱网与离线:数据一致性怎么保证

离线场景下最怕的是“同一份数据两个版本”。比如安检员在手机上改了检查项,同时内勤在后台也改了同一个任务的状态,两边同步时就会冲突。

解决思路是“客户端为主,服务端做版本校验”。每条业务数据带一个version字段,客户端提交时带上本地版本号,服务端比对,一致则更新版本号加一,不一致则返回冲突,客户端弹出提示让用户选择保留哪一版。对于安检记录这种以现场为准的数据,规则可以简化为“服务端永远接受客户端的检查项结果,但拒绝客户端修改任务状态”,把冲突面压到最小。

同步顺序也有讲究。必须先同步父实体再同步子实体,比如先传任务签到记录,再传检查项,最后传隐患和照片。照片单独走一条队列,因为体积大、耗时长,不能阻塞主流程。照片上传失败不影响表单数据落库,界面上提示“照片待上传”即可。

4.2 定位精度与防作弊的平衡

定位这块要接受一个现实:室内GPS基本不准,误差几十米很正常。所以电子围栏的半径不能设太小,否则误判率会高到无法使用。我的经验值是城市小区场景下半径设80到120米,管网线性点位设50米加路线校验。

防作弊不能只靠技术手段,还要靠制度配合。技术上能做的是:拍照禁止选相册、水印含服务端下发的时间戳、签到时间与照片EXIF时间交叉校验、连续多个点位打卡的时间间隔做合理性检查(比如两个相距2公里的点位间隔30秒,明显异常)。制度上是抽检加考核,系统只负责提供证据。

4.3 图片存储与带宽的成本控制

这是个纯算账的问题,但很多项目上线后才发现存储费用失控。算一笔账:假设某公司有8万户民用户,一年一轮安检,平均每户拍6张照片,单张压缩后300KB。年新增照片量是 80000 × 6 × 300KB ≈ 144GB。如果对象存储做三副本、再算上历史数据保留三年,总容量大概在1.3TB左右。这个量级用标准对象存储,成本完全可控。

关键在于上传带宽。安检员集中在下午5点到7点回传数据,假设500人同时上传,人均20张照片,单张300KB,总上传量约3GB,集中在两小时内,平均带宽需求约3.3Mbps,峰值可能到10Mbps以上。这个量级对服务端不算大,但对安检员的手机流量是负担。

所以图片压缩必须做在客户端。策略是:长边限制在1280像素,JPEG质量70,这样单张通常在150到300KB之间。签名图单独处理,宽度800像素、PNG质量中档,单张几十KB。压缩参数不要写死,做成服务端下发配置,方便后续按实际情况调整。

5. 落地实施:从原型到上线的关键动作

5.1 表单配置化先行,别急着写业务代码

项目启动后的第一件事,我建议是把检查项清单整理出来,做成配置表。这个过程会倒逼业务方把标准说清楚,也能提前暴露“这个项到底谁说了算”的问题。配置表至少包含:检查项编码、名称、类型、选项、是否必填、是否拍照、关联隐患等级、所属分组、排序号、生效版本。

整理完之后,用一份样例JSON跑通动态渲染,让安检员试用半天,收集反馈再定稿。这个环节花两三天,能省掉后期反复改需求的两三周。

5.2 接口设计:批量提交优于逐条提交

移动端同步接口不要设计成一条一条提交,网络开销和事务次数都受不了。正确做法是批量接口,一次提交一个任务的完整数据包:任务信息、检查项数组、隐患数组、照片ID数组。服务端在一个事务里处理,失败整体回滚,返回逐条结果。

接口大致长这样:

POST /api/v1/inspection/sync { "taskLocalId": "b7f3c2e1-...", "taskId": 100234, "checkinTime": "2024-06-11T09:23:11+08:00", "location": {"lng": 116.3, "lat": 39.9, "acc": 35}, "items": [ {"code": "HOSE_AGING", "value": "N", "remark": "", "photoIds": []}, {"code": "STOVE_PROTECT", "value": "Y", "remark": "无熄火保护", "photoIds": ["p1"]} ], "hazards": [ {"code": "HZ001", "level": "MAJOR", "desc": "灶具无熄火保护", "photoIds": ["p1"]} ], "signatureId": "s-8891" }

响应里对每个子项返回处理结果,客户端根据结果更新本地状态。这样即使部分数据有问题,也不影响整体进度。

5.3 灰度上线:先跑一个网格

不要全量铺开。选一个网格、二十来个安检员,跑两周。这两周重点看三件事:单户耗时、同步失败率、隐患录入完整率。单户耗时超过10分钟就要优化表单结构;同步失败率超过3%要查网络和接口超时设置;隐患录入完整率低于90%说明现场培训不到位或者表单引导有问题。

灰度期间一定要有人盯后台日志,特别是同步失败的具体报错。很多问题在客户端看不出来,只有看服务端日志才发现是字段长度超限或者时间格式不兼容。

6. 现场踩坑实录与排查速查表

6.1 那些上线后才暴露的问题

第一个坑:时间格式。客户端传的是带时区的ISO字符串,服务端某个字段用了datetime类型,某些数据库驱动会悄悄截断时区,导致跨时区或者夏令时场景下时间偏移。统一用UTC存储、展示时转换,能避免绝大多数问题。

第二个坑:字段长度。用户备注字段设了varchar(200),结果安检员写了一大段整改说明,提交失败,客户端只显示“同步失败”,安检员以为是网络问题反复重试。后来把错误信息透传到客户端,并把备注字段改成text,问题消失。教训是:所有用户可输入的字段,长度上限要在客户端就做限制并明确提示。

第三个坑:照片丢失。早期版本照片上传和表单提交是两个接口,表单先成功、照片后失败,结果隐患有记录没照片。后来改成表单先提交但标记“证据待补”,照片成功后再回调更新状态,界面上明确显示待补数量。

第四个坑:重复派单。定时任务跑重了,同一用户生成两条任务。加了唯一索引才彻底解决。

现象可能原因排查方向处理办法
同步一直失败字段超长或格式不符看服务端返回的具体错误码客户端做前置校验,服务端错误信息透传
照片上传卡住单请求体积过大或超时太短查看网关超时配置改分片上传,调大超时
任务列表空白数据范围权限配置错误检查用户所属组织路径修正组织归属或缓存刷新
打卡位置异常室内GPS漂移对比历史定位精度放宽容差,加人工核查标记
隐患统计对不上口径不一致或状态字段混乱核对状态机定义统一口径,废弃冗余状态
APP闪退本地库升级未做迁移查看崩溃日志补数据库版本迁移脚本

6.2 几条不太写在文档里的经验

第一,安检员的手机型号五花八门,低端机占比不低。APP的图片处理一定要放在后台线程,主线程做压缩必卡。内存峰值控制在200M以内,超过就容易被系统杀掉。

第二,表单配置的修改要有审批和版本记录。我见过配置被误改导致所有新任务少了一项检查,两周后才发现。加个简单的发布流程,配置改动走“草稿-审核-发布”,成本很低。

第三,报表数字出来之前,先和业务方手工核算一遍。有一次系统算出的整改率是92%,业务方手工算是78%,查了半天发现是系统把“已上报未复核”也算成了已整改。口径这东西,不核对永远不知道差在哪。

第四,别小看培训。同样的APP,培训到位的班组数据质量能高出一大截。建议做一个三分钟的录屏教程,重点讲离线怎么用、拍照有什么要求、隐患怎么建,比发一份PDF文档管用得多。

第五,数据库的软删除要慎用。任务和检查记录建议物理保留、用状态标记作废,不要真删。燃气行业的安检记录是有留存要求的,删了后面查不到,麻烦很大。

7. 后续可以往下延展的方向

这套系统跑稳之后,能接的东西不少。比如把安检数据和质量数据打通,用户报修时自动带出最近一次安检记录,维修人员上门前就知道这户的管道和灶具情况。再比如把隐患数据和巡检数据做关联分析,某个小区连续出现软管老化,可能是批次问题,可以推动集中更换。还有就是把用户端做成小程序,安检完成后用户能查到自己的安检报告和隐患整改建议,减少电话回访量。

不过我个人建议,先把一个网格的闭环跑顺、数据跑准,再谈扩展。燃气这行对数据的准确性要求很高,一条错误的隐患记录可能引发一次不必要的停气,用户投诉能追到你头上。稳一点,慢一点,把每个字段的业务含义搞清楚,比堆功能重要得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进