ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

RFID固定资产管理系统:从超高频选型到Java后台避坑实录

RFID固定资产管理系统:从超高频选型到Java后台避坑实录

简介:这是一套面向本科毕业设计的Java后台源码,基于Spring Boot框架打造RFID驱动的高校与单位固定资产登记追踪系统,围绕资产标签绑定、入库登记、领用归还、位置变更、盘点扫描与状态查询等核心流程展开。系统以RESTful接口对接常见RFID读卡器,并内置基础权限管理与操作日志,可适配高校实验室、行政单位及中小型企业的固定资产管理场景。压缩包共67个文件、约65KB,其中55个Java文件承载业务逻辑与接口实现,6个XML文件负责Maven构建及映射配置,另有YML环境参数、中英文双语文档、测试代码与Git忽略配置,按标准Maven目录组织,导入IDE即可运行调试。源码结构清晰,覆盖完整后端闭环,适合作为Java毕设选题参考、二次开发底座或课程综合实践项目。目前已有41人学习下载,对准备RFID资产管理方向论文与系统的在校生颇具参考价值。

1. RFID驱动的高校/单位固定资产登记与追踪系统,翻车点不在Java后台

这个标题看起来是典型的 Java 后台项目,真正做完一遍的人都知道:后台代码三个月能写完,现场跑通却要半年。金属柜上贴的标签扫不到、批量盘点时同一张卡被读写器反复上报、资产被领用后从账本里直接消失——这四类问题不解决,RFID 驱动的高校/单位固定资产登记与追踪系统演示得再漂亮也是一堆摆设。这个项目要解决的本质是账实不符:用超高频读写器扫一遍房间,后台自动比对出在账、盘盈、盘亏,并按资产的流转记录追踪去向。适合正在做 Java 毕业设计,或者单位内部要做资产盘点工具的开发同学。整套系统从数据库到接口都不复杂,复杂的是先想清楚 RFID 的数据长什么样、现场有哪些物理限制。

2. 选型与设计:写固定资产后台前,先定频段、表结构和读写器对接方式

很多开发者的习惯是上手就建工程写代码,写到盘点导入数据时才卡住:RFID 标签数据长什么样?读上来的字符串是 HEX 还是 ASCII?资产表主键用编号还是用标签号?这些问题在设计期没定,返工成本很高。我一般花半天把下面三件事定清楚,再打开 IDE。

2.1 先选频段:高校资产批量盘点为什么绕不开超高频 UHF

市场上主流的 RFID 分两个频段:高频 HF 13.56 MHz 和超高频 UHF 860-960 MHz。高校固定资产场景基本只有超高频一个正确选项,原因在读取距离和盘点速度。

频段频率范围典型读取距离盘点方式适用对象
高频 HF13.56 MHz5~10 cm单件贴近读门禁卡、贵重仪器定点签退
超高频 UHF860-960 MHz3~10 m批量读、快速扫固定资产整屋盘点、通道门追踪

一台超高频手持机站在过道中间,可以覆盖左右两排设备的标签;高频则需要贴着标签一台一台刷,几千件资产会刷到怀疑人生。UHF 的批量识别能力是每秒上百张标签,这才是"登记与追踪"能落地的核心前提。

标签类型也要在选型阶段定掉:普通纸质标签贴木桌、塑料外壳没问题,遇到金属机箱、铁皮柜必须换抗金属标签。超高频信号在金属表面会反射和衰减,标签天线直接贴在金属上等于没用。抗金属标签带硬质垫层,把天线和金属隔开,虽然厚一点、贵一点,但贴上去基本一劳永逸。这个细节对应后面第 4 章的第一个坑。

读写器的功率参数同样值得提前说:发射功率 25~33 dBm 可调。25 dBm 读距近,适合桌面级测试;33 dBm 读距远,适合整屋盘点。但一个房间装多台固定式读写器时,别把功率全开满,相邻设备互相干扰会产生"读空气"现象——扫到隔壁房间或楼下的标签,盘点差异表瞬间多出一堆不存在的资产。常见做法是:手持机盘点时开足功率,通道门检测时把固定式读写器功率调低,只覆盖门口一米范围。

2.2 数据模型:标签绑定表和资产主表必须分开建

先搞清楚超高频标签上有两段数据:EPC 和 TID。EPC 是 96 位,十六进制表示就是 24 个字符,这一段允许按自己的编码规则写业务信息;TID 是出厂写入的,每张卡唯一且不可改。做固定资产追踪,我用 TID 做唯一性校验,防止同一张卡重复绑定;用 EPC 做盘点时的业务查询键;资产编号则放在数据库里通过绑定关系关联,而不是塞进 EPC。

这里有一个常见的误设计:把 EPC 直接当成资产主键。资产可能换标签,第一次绑定的 EPC 是 A,标签损坏后换了 EPC B。如果 asset 表里直接存 EPC 当唯一标识,换标签就要改主键,所有历史流水全部错乱。正确做法是三张表:

  • t_asset:资产本身的信息,资产编号、名称、分类、部门、存放位置、状态。
  • t_rfid_tag:标签绑定关系,资产 ID、EPC、TID、读写器编号、绑定时间。EPC 和 TID 都加唯一索引。
  • t_movement:追踪流水表,资产 ID、操作类型、读写器编号、位置、经办人、发生时间。这张表只增不删,一次盘点、一次领用、一次出门都落一条流水。

"追踪"两个字就靠 t_movement 这张表实现。后面做盘点差异、出门检测、资产履历查询,全是从这张表读数据。资产主表反映的是当前状态,流水表反映的是历史轨迹,两者职责分开,系统才不会越写越乱。

2.3 后台技术栈与对接方式:用适配层把不同读写器挡在门外

后台技术栈我一般用 Spring Boot 2.7 或 3.x,MySQL 8,MyBatis Plus 做数据访问。Redis 不是必须的,如果演示机器内存小,窗口去重完全可以用数据库查询实现,第 5 章会给出两种写法。

真正的设计重点在读写器对接。市面上超高频读写器的接入方式无非三种:串口、TCP Socket 自定义协议、HTTP 回调。厂商 SDK 各不相同,但底层都在做同一件事:把读到的一组标签 EPC 和信号强度上报给后台。因此后台不要直接依赖某一家厂商的 SDK,而是定义一个统一的标签上报体:

{ "readerSn": "RDR-001", "epcList": ["300833B2DDD9010000000001", "300833B2DDD9010000000002"], "rssiList": [-60, -63], "readTime": "2025-01-01 10:00:00" }

readerSn 是读写器编号,epcList 是一批标签 EPC,rssiList 是对应信号强度,readTime 是读取时间。后台业务只认这个结构,厂商 SDK 的差异全部收在一个 ReaderAdapter 接口里。硬件没到货时写一个 mock 实现,盘点、追踪、差异比对全流程照样能开发;硬件到场后替换适配器实现,业务代码一行不用改。这个习惯在最后一章还会再展开。接口一旦定下来,所有盘点、追踪逻辑就和具体设备解耦了。

3. 后台代码落地:RFID标签绑定、登记入库与追踪查询的Java实现

设计定完,下面是能直接抄进 Spring Boot 项目的最小实现。我按建表、绑定服务、盘点上报、追踪查询四个步骤拆开讲,每段代码都带注释和参数说明。

3.1 建表:三张表把资产、RFID 标签、追踪流水分开存

先建这三张核心表,字段按最小可用标准设计,后续要加扩展字段直接 ALTER TABLE 就行。

-- 资产主表 CREATE TABLE t_asset ( id BIGINT AUTO_INCREMENT PRIMARY KEY, asset_code VARCHAR(32) NOT NULL COMMENT '资产编号,纸面资产台账同步', asset_name VARCHAR(128) NOT NULL COMMENT '资产名称', category VARCHAR(32) COMMENT '分类:仪器/电子设备/家具', dept VARCHAR(64) COMMENT '使用部门', location VARCHAR(128) COMMENT '存放位置描述', status TINYINT DEFAULT 0 COMMENT '0在库 1领用 2维修 3报废', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_asset_code (asset_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资产登记主表'; -- RFID 标签绑定表 CREATE TABLE t_rfid_tag ( id BIGINT AUTO_INCREMENT PRIMARY KEY, asset_id BIGINT NOT NULL COMMENT '关联 t_asset.id', epc VARCHAR(32) NOT NULL COMMENT '超高频 EPC 十六进制字符串,统一大写', tid VARCHAR(32) NOT NULL COMMENT '标签 TID,出厂唯一', reader_sn VARCHAR(64) COMMENT '绑定操作使用的读写器编号', bind_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_epc (epc), UNIQUE KEY uk_tid (tid), KEY idx_asset_id (asset_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='RFID标签与资产绑定表'; -- 追踪流水表 CREATE TABLE t_movement ( id BIGINT AUTO_INCREMENT PRIMARY KEY, asset_id BIGINT NOT NULL COMMENT '关联 t_asset.id', action_type VARCHAR(16) NOT NULL COMMENT 'BIND/SCAN/OUT/IN/CHANGE', reader_sn VARCHAR(64) COMMENT '读写器编号', location VARCHAR(128) COMMENT '盘点或操作时的位置', operator VARCHAR(32) COMMENT '操作人或经办人', remark VARCHAR(255) COMMENT '备注', happen_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_asset_time (asset_id, happen_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资产追踪流水表';

逻辑说明:t_rfid_tag 里 asset_id 没有加唯一索引,因为一张资产允许换绑多个标签,但历史绑定会通过流水保留;epc 和 tid 的唯一索引是硬约束,同一张卡重复绑定直接报错。t_movement 的 action_type 用字符串不用枚举,因为盘点、领用、维修、报废这些操作后续还要扩展,字符串类型加注释最灵活。

参数说明:epc 字段设计为 VARCHAR(32),24 个十六进制字符完全放得下,留一点余量应对 128 位 EPC 的标签。TID 同样预留 32 位。asset_code 用的 VARCHAR(32),覆盖"部门缩写+年份+流水号"这类编码规则。

3.2 绑定服务:把 EPC、TID、资产编号对应起来

绑定是登记环节的核心操作:资产信息先进 t_asset,然后拿一张物理标签贴到设备上,通过读写器读出 EPC 和 TID,调用绑定接口建立关联。

@Service @Slf4j public class RfidBindingService { @Resource private AssetMapper assetMapper; @Resource private TagMapper tagMapper; @Resource private MovementMapper movementMapper; /** * 绑定 RFID 标签到指定资产 * @return 资产ID */ @Transactional(rollbackFor = Exception.class) public Long bindTag(BindRequest req) { // EPC 96位转十六进制正好24个字符,这里先做格式校验 if (!isValidEpc(req.getEpc())) { throw new BusinessException("EPC格式不正确,应为24位十六进制字符串"); } // 用TID查重,防止同一张物理标签被绑到多个资产上 Tag existTag = tagMapper.selectOne( Wrappers.lambdaQuery(Tag.class) .eq(Tag::getTid, req.getTid().toUpperCase())); if (existTag != null) { if (!existTag.getAssetId().equals(req.getAssetId())) { throw new BusinessException("该标签已绑定到其他资产"); } return existTag.getAssetId(); } // 资产必须真实存在,避免脏关联 Asset asset = assetMapper.selectById(req.getAssetId()); if (asset == null) { throw new BusinessException("资产不存在"); } // 写入绑定关系,EPC统一大写,后续所有查询都按大写走 Tag tag = new Tag(); tag.setAssetId(asset.getId()); tag.setEpc(req.getEpc().toUpperCase()); tag.setTid(req.getTid().toUpperCase()); tag.setReaderSn(req.getReaderSn()); tagMapper.insert(tag); // 绑定动作本身落一条流水,保证资产履历可回溯 Movement mv = new Movement(); mv.setAssetId(asset.getId()); mv.setActionType("BIND"); mv.setReaderSn(req.getReaderSn()); mv.setLocation(asset.getLocation()); mv.setRemark("绑定标签EPC:" + tag.getEpc()); movementMapper.insert(mv); return asset.getId(); } private boolean isValidEpc(String epc) { return epc != null && epc.length() == 24 && epc.toUpperCase().matches("^[0-9A-F]{24}$"); } }

逻辑说明:EPC 校验是必须的,读写器返回的数据偶尔会带换行符或空格,长度不匹配就直接拒绝,避免脏数据进库。TID 查重放在 EPC 校验之后,因为 EPC 可以重复但 TID 理论上全球唯一,用 TID 做物理卡防重最可靠。事务注解保证标签绑定和流水插入要么同时成功、要么同时回滚。

参数说明:req 对象里有 assetId、epc、tid、readerSn 四个字段。readerSn 记录是哪台读写器完成的绑定,将来排查"这批货是谁贴的标签"时有用。isValidEpc 里的正则^[0-9A-F]{24}$严格限定 24 位十六进制字符,大小写在匹配前已经统一处理。

3.3 盘点上报接口:标签扫到后自动登记流水

盘点场景是读写器持续上报标签,后台接口会被高频调用。这个接口要同时处理"未绑定标签"和"重复上报"两种异常情况。

@RestController @RequestMapping("/api/rfid") public class RfidScanController { @Resource private TagMapper tagMapper; @Resource private MovementMapper movementMapper; @Resource private ScanLogMapper scanLogMapper; /** * 读写器批量上报入口 */ @PostMapping("/scan") public Result handleScan(@RequestBody ScanReport report) { // 同一个包内先做字符串去重,读写器一次上报可能带重复标签 List<String> epcList = report.getEpcList().stream() .map(String::trim) .distinct() .collect(Collectors.toList()); for (String epc : epcList) { scanOne(report.getReaderSn(), epc); } return Result.ok(epcList.size()); } private void scanOne(String readerSn, String epc) { // 查标签绑定关系,注意EPC统一转大写 Tag tag = tagMapper.selectOne( Wrappers.lambdaQuery(Tag.class) .eq(Tag::getEpc, epc.toUpperCase())); if (tag == null) { // 未绑定标签单独记异常日志,不能写入movement,因为movement强关联asset_id scanLogMapper.insert(new ScanLog(epc.toUpperCase(), readerSn, "UNBOUND")); return; } // 同一资产30秒窗口内只记第一条SCAN流水,避免读写器反复上报刷爆表 LocalDateTime windowStart = LocalDateTime.now().minusSeconds(30); Long count = movementMapper.selectCount( Wrappers.lambdaQuery(Movement.class) .eq(Movement::getAssetId, tag.getAssetId()) .eq(Movement::getActionType, "SCAN") .ge(Movement::getHappenTime, windowStart)); if (count != null && count > 0) { return; } Movement mv = new Movement(); mv.setAssetId(tag.getAssetId()); mv.setActionType("SCAN"); mv.setReaderSn(readerSn); mv.setLocation(tag.getAssetLocation()); // 资产当前登记的位置 movementMapper.insert(mv); } }

逻辑说明:入口先distinct()去重,解决读写器单包重复;未绑定标签单独写入 t_scan_log,而不是强插 t_movement,因为 movement 表外键关联 asset_id,未绑定的标签根本没有资产,强行插入会破坏数据完整性。30 秒窗口去重是这套代码里最重要的防刷逻辑:一台固定式读写器每秒可能上报十几次同一标签,没有窗口约束的话盘点一小时能产生几十万条流水。

参数说明:30 秒窗口值不是拍脑袋定的。手持机扫过一台设备时,标签通常会被读到三五次,间隔在毫秒级;盘点人员移动到下一台设备时,上一台设备已经离开读取范围。30 秒足够覆盖单台设备的所有重复上报,又不会误伤"同一位置重复盘点"的合法场景。如果现场有多台固定式读写器同时工作,窗口缩到 10 秒更合适。

3.4 追踪查询:一条资产从到货到报废的全量记录

追踪查询是系统演示时最高频的功能:输入资产编号,页面上能看到这台设备什么时候入账、什么时候盘点过、最后一次出现在哪台读写器附近。

SELECT a.asset_code, a.asset_name, m.action_type, m.reader_sn, m.location, m.operator, m.remark, m.happen_time FROM t_movement m JOIN t_asset a ON a.id = m.asset_id WHERE m.asset_id = #{assetId} ORDER BY m.happen_time DESC;

逻辑说明:关联查询一次拿全资产信息和流水信息,MyBatis 里直接写在 XML 或注解中即可。按 happen_time 倒序排列后,列表第一条就是该资产最近一次被读写器读到的记录,可以推断它当前大致在哪。BIND 表示入账绑定,SCAN 表示盘点时被读到,OUT/IN 表示出门和归还,这样资产履历一目了然。

参数说明:#{assetId} 是 t_asset 表的主键。如果前端拿的是资产编号而不是主键,可以先查一次 t_asset 拿到 id 再执行这条 SQL。数据量超过十万条流水时,idx_asset_time 索引能保证这个查询稳定在几十毫秒内返回。

4. 避坑记录:固定资产 RFID 系统最容易踩的五个坑

这一章全部来自现场反馈和同行交流,每一条都按现象、原因、解决三个层次展开。做固定资产 RFID 系统的人,基本都会在这五个位置至少翻一次车。

4.1 金属柜门上的标签永远扫不到

现象:统一采购的纸质 RFID 标签,贴在金属机柜、实验仪器外壳上,手持机怎么扫都只有零星响应,贴在同一区域木柜上的标签却能正常读取。

原因:超高频电磁波遇到金属平面会发生反射和涡流损耗,标签天线与金属表面之间无法建立有效的电磁场,能量全部被金属"吃掉"。

解决:金属表面换抗金属标签,这种标签自带硬质垫层,把天线垫高几毫米,避免直接贴合金属。不要指望垫泡沫双面胶能解决问题,现场施工时胶层厚度很难控制,垫了也容易脱落。正确流程是批量采购前先买几片样品,贴在目标资产上实测读取距离和成功率,再决定采多少。实验室金属外壳设备多的话,抗金属标签的费用要提前算进预算。

4.2 EPC 大小写不一致导致资产查不到

现象:绑定阶段写入的 EPC 是大写,盘点时手持机协议返回小写十六进制,后台按大写去查绑定表,永远查不到,这台资产的盘点结果一直显示"未扫码"。

原因:不同厂商读写器的字符串格式不统一,有返回大写的,有返回小写的,个别设备甚至返回带冒号分隔的格式。这些差异在联调阶段发现不了,因为 demo 程序都用自己的 SDK。

解决:所有入口统一转大写,并且只在数据入口处转一次。绑定接口、扫盘接口、模拟器接口,全部对 EPC 调toUpperCase(),数据库里也统一存大写。不要在查询时做转换,否则每个 SQL 都要人肉记得转,迟早漏一两个地方。我通常还会建一条数据库约束,强制 epc 字段只能存大写,从存储层面把格式锁死。

4.3 批量上报重复入库,流水表被刷爆

现象:一台读写器站在房间里扫一圈,t_movement 表里出现同一资产几十条 SCAN 流水,盘点差异数完全没法看,页面加载也变慢。

原因:超高频读写器是连续读取模式,标签只要在读取范围内,每秒可能被上报十几次。如果盘点逻辑是"扫到就插流水",一个下午就能产生百万条无效数据。

解决:按 3.3 节的方式做窗口去重,同一个资产 30 秒内只保留一条 SCAN 流水。更严谨的做法是加 Redis 幂等键,用 SETNX 控制窗口;没有 Redis 环境时数据库时间窗口查询也能兜住。如果还有并发推送的极端场景,就在 t_movement 上建一个"asset_id + action_type + happen_time 分钟级别"的唯一索引,从数据库层面拒绝重复。

4.4 领用后资产从盘点列表里消失

现象:设备被某人领用后,状态改成"领用",下次盘点时这台设备不在原房间,系统里也查不到它的位置,资产在账本里"悬空"。

原因:领用登记只改了 t_asset 的 status 字段,没有落一条 OUT 流水,也没有记录资产被带到了哪里。盘点逻辑只统计"状态为在库"的资产,领用中的设备既不参与盘点,也没有位置信息,自然就从账本里蒸发了。

解决:领用动作必须走一个独立接口,业务上做两件事:更新资产状态为"领用"、写一条 action_type=OUT 的流水,流水里带上领用人和去向部门。盘点差异表要增加"缺失但已领用"这个分类,而不是直接判定盘亏。这样资产从"在库"变成"已被某人领走",每一步都有据可查。

4.5 盘点结果直接覆盖资产表,误操作没有后悔药

现象:第一次盘点时手持机漏读了某排货架,系统把一批正常在账的资产批量标记为"盘亏";第二天补扫后状态又恢复正常,但盘点历史已经被污染,审计时说不清为什么会有一次虚假盘亏。

原因:盘点保存逻辑直接 UPDATE t_asset 的 status 或"最后盘点时间"字段,没有做"盘点批次-差异确认"的中间层。漏读、标签损坏、设备临时故障都会直接改掉资产主表。

解决:盘点只负责生成差异明细表和差异汇总,盘点人核对确认后,系统才允许更新资产状态,并且每次更新都落一条 CHANGE 流水。留出"确认"这一步,相当于给误盘留了一条后悔药,审计也能看到"哪一次盘点在确认前被作废"。固定资产盘点最重要的是过程可追溯,不是结果一次到位。

5. 盘点与追踪的功能设计:去重、差异表和出门检测怎么落地

登记做完,系统最大的价值体现在盘点和追踪两个功能。盘点解决"账实是否一致",追踪解决"资产在哪个环节、去过哪里"。这一章把这两个功能的设计思路细化到可以编码。

5.1 盘点:批次会话 + 窗口去重 + 差异对比

盘点功能不能做成"扫到啥算啥",正确做法是分三步走:建一个盘点批次,关闭批次,最后做差异对比。

第一步,发起盘点时在后台创建一个盘点批次,记录盘点范围、盘点人、开始时间。第二步,手持机扫码时,前面 3.3 节的 scan 接口需要带上 batchId,通过 Redis 做窗口去重:

// 盘点批次内同一标签30秒只处理一次 String dedupKey = "inv:" + batchId + ":" + epc; Boolean first = redisTemplate.opsForValue() .setIfAbsent(dedupKey, "1", 30, TimeUnit.SECONDS); if (Boolean.TRUE.equals(first)) { // 第一次出现的标签才进入盘点统计 handleAssetHit(tag.getAssetId(), batchId, readerSn); }

逻辑说明:Redis 的 SETNX 语义天然适合做窗口去重,key 带 batchId 让每个盘点批次独立计数,过期时间 30 秒和 3.3 节保持一致。没有 Redis 环境的项目,把这段换成 3.3 节里的数据库时间窗口查询,效果相同,只是并发量高时多几条 SELECT 而已。

第三步,盘点结束后,把所有扫到过的 assetId 集合与应盘资产清单做差集:扫到但清单里没有的是盘盈,清单里有但没扫到的是盘亏。差异结果先落一张盘点差异表,不做任何状态更新,等人工确认后再按 4.5 节的方式处理。

5.2 出门检测:位置追踪的低成本做法

固定资产的"追踪"不要求实时坐标,把每一个关键出入口管住就够了。常见做法是在房间门口或楼道出入口安装固定式读写器,资产被抬出门时自动记录经过哪道门、什么时间。

方向判断是这里唯一的技术难点。单个固定式读写器只能判断"标签出现过",无法区分是进门还是出门。常见做法是配合红外对射传感器:先遮挡红外再读到标签,判定为出门;先读到标签再遮挡红外,判定为进门。后台收到 OUT 事件先别急着改状态,落一条 pending 流水并推送提醒,管理员确认是"正常领用外出"还是"未登记出门"后再更新资产状态。这个确认步骤能把正常搬运和可能的违规出门区分开,避免系统误报警。

5.3 和常见误用的差别:盘点不是改资产状态,追踪不是 GPS

做类似系统时最容易产生两个预期偏差。第一,盘点结果不能直接覆盖资产主表,盘点产出的是一份差异报告,资产状态的变更必须由人确认后执行。第二,RFID 追踪是"节点式追踪",不是实时 GPS——你知道的是资产最后一次出现在哪道门、哪个房间,而不是它此刻在哪个坐标点。很多刚入门的人做到一半开始补定位算法,然后翻车。高校固定资产管理的真实需求是"这台仪器在谁手里、最后一次出现在哪里",节点式追踪完全够用,而且实现成本低一个数量级。

6. 没有 RFID 硬件时先把后台跑通:模拟读写器与联调技巧

硬件没到货是整个开发周期里最容易干等的阶段。很多开发者在这里浪费两三周,最后联调时间被压缩到一两天。正确做法是写一个模拟读写器接口,把随机生成的 EPC 数据送进后台,让绑定、盘点、追踪全链路先跑起来。

6.1 用模拟读写器接口替代硬件

模拟接口本质就是一个 POST 端口,触发时自动向 scan 接口推一批假标签:

@RestController @RequestMapping("/mock/reader") public class MockRfidReaderController { @Resource private RfidScanController rfidScanController; @PostMapping("/scan") public Result mockScan(@RequestParam(defaultValue = "MOCK-001") String readerSn) { List<String> epcList = List.of( "300833B2DDD9010000000001", "300833B2DDD9010000000002", "300833B2DDD9010000000003" ); ScanReport report = new ScanReport(); report.setReaderSn(readerSn); report.setEpcList(epcList); return rfidScanController.handleScan(report); } }

逻辑说明:模拟接口直接调用真实的 handleScan 方法,意味着去重、未绑定标签处理、流水写入全部走正式代码路径。唯一换掉的是数据来源。等真实读写器到场后,写一个厂商适配器,把 SDK 回调的数据组装成 ScanReport 再交给 handleScan,业务层一行不改。

6.2 用 curl 一条条验证接口

没有前端页面时,curl 是最快的验证工具。绑定服务和盘点上报接口都可以用一条命令触发:

curl -X POST http://localhost:8080/api/rfid/scan \ -H 'Content-Type: application/json' \ -d '{"readerSn":"MOCK-001","epcList":["300833B2DDD9010000000001","300833B2DDD9010000000002"]}'

调通后查 t_movement 表,应该能看到对应的 SCAN 流水;再查 t_scan_log,能看到那些绑定表里不存在的 EPC 被完整记录。整个联调闭环不依赖任何硬件,这也是我后来做所有硬件对接项目固定下来的习惯:先定接口、再写业务逻辑、最后对接设备。曾经有一次设备到货前一天,演示全靠 mock 接口撑住,真实读写器接入只用了半个下午,核心代码一行没动。希望这份经验也能帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

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