☰
基于RFID的国有资产管理系统Java源码实战:标签绑定、盘点与硬件对接
2026/10/31 6:05:30 网站建设 项目流程

简介:这份资源是面向Java方向本科毕业设计的后台项目源码,主题为基于RFID技术的国有资产管理系统,适合正在准备毕设、需要完整可运行工程参考的计算机专业学生,也可供想了解RFID资产盘点、借还、报废等业务流程与后台实现思路的开发者学习。压缩包共65个文件,以55个Java源文件为核心,配合6个XML配置、1个yml配置文件、2个md说明文档和1个txt文本,整体约54KB,属于轻量级后台工程,便于快速导入IDE阅读与二次修改。项目采用Maven结构组织,包含pom.xml与src主测试目录,代码分层清晰,可帮助读者理解资产信息管理、RFID标签数据对接、权限与基础业务模块的实现方式。目前已有1098人学习下载,可作为毕设选题落地、代码框架搭建与功能扩展的参考素材。

1. 从一张资产标签说起:RFID 国有资产管理系统到底在管什么

很多做 Java 毕业设计的同学,第一次听到「基于 RFID 技术的国有资产管理系统」时,脑子里浮现的是超市防盗门那种滴滴响的东西。其实不是。RFID 在这里解决的是一个非常具体的痛点:单位里几万件固定资产,从服务器、打印机到办公桌椅,每年盘点靠人拿着 Excel 挨个房间对,对完还要手动录入,错漏率高得离谱。这套系统的核心逻辑,是把每一件资产和一个 RFID 电子标签绑定,用读写器批量识别,后台用 Java 完成资产入库、领用、调拨、报废、盘点的全生命周期管理。

这个标题对应的是一套后台项目源码,技术栈通常是 Spring Boot + MyBatis + MySQL,前端可能是 Vue 或 Thymeleaf,硬件侧对接的是 13.56MHz 或 860-960MHz 的 RFID 读写器。适合谁看?正在做 Java 课程设计或毕业设计、需要一套能跑起来、能讲清楚、能应付答辩的完整后台系统的同学,以及想了解 RFID 和业务系统怎么对接的初级开发。热词里那些 java 面试题、java 基础、spring boot + mybatis 多商户商城源码,说明搜这个标题的人多半也在补 Java 后端基本功,所以这篇会顺带把源码里真正值得抠的 Java 知识点讲透。

2. 后台源码拆开看:RFID 国有资产管理系统的分层与选型

拿到一个 zip 包,别急着双击运行。先搞清楚它为什么这么分层,比背代码重要得多。这套系统的后台典型结构是 controller → service → mapper → entity,外加一个 rfid 硬件适配层。选型上,Spring Boot 负责把 Tomcat 内嵌、自动装配、统一异常处理这些脏活干掉;MyBatis 负责把资产表和标签表的复杂查询写成可控的 XML;MySQL 存资产主数据、标签绑定关系、操作流水。为什么不用 JPA?因为资产盘点涉及大量多表关联和动态条件查询,MyBatis 的 XML 写起来更直观,答辩时也更容易讲清楚每条 SQL 在干什么。

2.1 资产、标签、流水三张核心表怎么设计

先看表结构,这是整个系统的地基。资产表存的是「东西本身」,标签表存的是「贴在东西上的电子身份证」,流水表存的是「谁在什么时候对它做了什么」。三张表通过 asset_id 和 tag_epc 关联。

-- 资产主表:一件资产一行 CREATE TABLE t_asset ( id BIGINT PRIMARY KEY AUTO_INCREMENT, asset_code VARCHAR(64) NOT NULL COMMENT '资产编号,人工可读', asset_name VARCHAR(128) NOT NULL COMMENT '资产名称', category_id BIGINT COMMENT '资产分类', status TINYINT DEFAULT 0 COMMENT '0在库 1领用 2调拨 3报废', dept_id BIGINT COMMENT '使用部门', keeper VARCHAR(32) COMMENT '保管人', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_asset_code (asset_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 标签绑定表:一个标签对应一件资产,EPC 全局唯一 CREATE TABLE t_rfid_tag ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tag_epc VARCHAR(96) NOT NULL COMMENT '标签EPC码,读写器读到的原始值', asset_id BIGINT COMMENT '绑定的资产ID,未绑定为NULL', bind_time DATETIME COMMENT '绑定时间', tag_status TINYINT DEFAULT 0 COMMENT '0未绑定 1已绑定 2已注销', UNIQUE KEY uk_epc (tag_epc), KEY idx_asset (asset_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 操作流水表:所有变更留痕,盘点差异靠它追溯 CREATE TABLE t_asset_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, asset_id BIGINT NOT NULL, action_type VARCHAR(32) NOT NULL COMMENT 'BIND/UNBIND/CHECKOUT/RETURN/SCRAP', operator VARCHAR(32), action_time DATETIME DEFAULT CURRENT_TIMESTAMP, remark VARCHAR(255) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

逻辑说明:t_asset 的 asset_code 加了唯一索引,防止同一件资产被重复录入;t_rfid_tag 的 tag_epc 也唯一,因为一个物理标签的 EPC 在出厂时就固定了,重复绑定一定是数据出了问题。参数上,tag_epc 给到 96 位是因为超高频标签的 EPC 区常见 96bit,转成十六进制字符串大概 24 个字符,留足余量。t_asset_log 不设外键,是为了避免盘点高峰期写入被外键检查拖慢,靠业务层保证一致性。

提示:很多同学从网上下的源码,表里没有唯一索引,跑起来没问题,一导入真实数据就出现一个标签绑了两件资产。加索引这一步别省。

2.2 用 Spring Boot 写一个标签绑定接口

标签绑定是这套系统最核心的动作:读写器读到 EPC,后台把它和某件资产关联起来。下面是一个精简但能跑的 Controller + Service 写法。

@RestController @RequestMapping("/api/rfid") public class RfidBindController { @Autowired private RfidTagService rfidTagService; /** * 绑定标签到资产 * @param epc 读写器读到的标签EPC * @param assetId 目标资产ID */ @PostMapping("/bind") public Result<Void> bind(@RequestParam String epc, @RequestParam Long assetId) { // 1. 校验EPC格式,防止前端传入脏数据 if (!epc.matches("^[0-9A-Fa-f]{8,24}$")) { return Result.fail("EPC格式不合法"); } // 2. 校验资产是否存在且未报废 Asset asset = rfidTagService.getAsset(assetId); if (asset == null || asset.getStatus() == 3) { return Result.fail("资产不存在或已报废"); } // 3. 执行绑定,内部处理并发和重复绑定 rfidTagService.bindTag(epc, assetId); return Result.ok(); } }
@Service public class RfidTagService { @Autowired private RfidTagMapper tagMapper; @Autowired private AssetLogMapper logMapper; @Transactional(rollbackFor = Exception.class) public void bindTag(String epc, Long assetId) { // 先查这个EPC当前状态 RfidTag tag = tagMapper.selectByEpc(epc); if (tag == null) { // 新标签,直接插入并绑定 tag = new RfidTag(); tag.setTagEpc(epc); tag.setAssetId(assetId); tag.setTagStatus(1); tag.setBindTime(new Date()); tagMapper.insert(tag); } else if (tag.getTagStatus() == 0) { // 已存在但未绑定,更新即可 tag.setAssetId(assetId); tag.setTagStatus(1); tag.setBindTime(new Date()); tagMapper.updateById(tag); } else { // 已绑定到别的资产,直接抛异常回滚 throw new BizException("该标签已绑定资产:" + tag.getAssetId()); } // 写流水,方便追溯 AssetLog log = new AssetLog(); log.setAssetId(assetId); log.setActionType("BIND"); log.setRemark("绑定EPC:" + epc); logMapper.insert(log); } }

逻辑说明:Controller 只做参数校验和结果包装,业务判断全在 Service。@Transactional 保证「更新标签 + 写流水」要么都成功要么都回滚,这是资产系统的基本要求。参数上,epc 的正则限制 8 到 24 位十六进制,是因为不同厂商读写器返回的 EPC 长度不一,太短可能是误读,太长可能是把 TID 区也读进来了。常见坑是并发绑定:两个请求同时读到同一个新 EPC,都走 insert 分支,第二个会撞唯一索引报错。解决办法是在 catch 到 DuplicateKeyException 后重试一次查询更新,或者直接用INSERT ... ON DUPLICATE KEY UPDATE。

2.3 盘点功能:批量读取和差异比对怎么落地

盘点是 RFID 相比条码最大的优势:不用一件件扫,读写器一次能读几十上百个标签。后台要做的是接收一批 EPC,和数据库里「应该在这个房间的资产」比对,算出盘盈盘亏。

@PostMapping("/inventory/check") public Result<InventoryResult> check(@RequestBody InventoryRequest req) { // req.epcList 是读写器本次读到的所有EPC // req.deptId 是本次盘点的部门范围 List<String> scanned = req.getEpcList(); // 查出该部门理论上应有的资产EPC List<String> expected = assetMapper.selectEpcsByDept(req.getDeptId()); Set<String> scannedSet = new HashSet<>(scanned); Set<String> expectedSet = new HashSet<>(expected); // 盘亏:账上有、实际没读到 List<String> loss = expectedSet.stream() .filter(e -> !scannedSet.contains(e)) .collect(Collectors.toList()); // 盘盈:实际读到、账上没有(可能是别的部门串过来的) List<String> surplus = scannedSet.stream() .filter(e -> !expectedSet.contains(e)) .collect(Collectors.toList()); InventoryResult result = new InventoryResult(); result.setLossList(loss); result.setSurplusList(surplus); result.setNormalCount(expectedSet.size() - loss.size()); return Result.ok(result); }

逻辑说明:用 HashSet 做差集,时间复杂度 O(n),几万条数据毫秒级完成。参数上,epcList 可能包含重复读取(读写器在标签停留时会反复上报),所以先转 Set 去重。盘盈列表不能直接当成错误,实际场景里经常是隔壁部门的资产被误读进来,需要人工确认。这里有个血泪经验:读写器功率调太大,会把走廊对面房间的标签也读进来,导致盘盈虚高。功率参数一般设在 20-26dBm 之间,具体看标签类型和距离。

3. 硬件对接:Java 后台怎么和 RFID 读写器说上话

后台系统本身不直接跟射频打交道,它面对的是读写器厂商提供的 SDK 或网络接口。这一步是很多同学翻车的地方:代码写完了,读写器连不上,或者读到的全是乱码。核心原因是没搞清楚通信方式和数据格式。

3.1 串口、网口、SDK 三种对接方式怎么选

常见做法有三种。第一种是串口(RS232/RS485),读写器通过 COM 口连电脑,Java 侧用 jSerialComm 或 RXTX 库读写字节流。第二种是网口(TCP),读写器作为 TCP Server 或 Client,Java 用 Socket 通信,适合读写器离服务器远的场景。第三种是厂商 SDK,通常是一个 jar 包加几个 dll,调用封装好的方法。毕设场景我一般推荐网口 TCP,因为不依赖特定操作系统,代码也最好讲。

// 网口读写器:建立TCP连接并发送盘存命令 public class RfidTcpClient { private Socket socket; private OutputStream out; private InputStream in; public void connect(String ip, int port) throws IOException { socket = new Socket(); // 连接超时3秒,避免读写器没开机时一直卡住 socket.connect(new InetSocketAddress(ip, port), 3000); socket.setSoTimeout(2000); // 读超时2秒 out = socket.getOutputStream(); in = socket.getInputStream(); } /** * 发送盘存指令,返回读到的EPC列表 * 指令格式因厂商而异,这里以常见的0x01开头为例 */ public List<String> inventory() throws IOException { byte[] cmd = new byte[]{(byte) 0x01, (byte) 0x00, (byte) 0x00}; out.write(cmd); out.flush(); List<String> epcs = new ArrayList<>(); byte[] buf = new byte[1024]; long deadline = System.currentTimeMillis() + 1000; // 采集1秒 while (System.currentTimeMillis() < deadline) { try { int len = in.read(buf); if (len > 0) { epcs.addAll(parseEpcs(buf, len)); } } catch (SocketTimeoutException e) { // 超时说明这一轮读完了,正常退出 break; } } return epcs; } }

逻辑说明:connect 里设置连接超时和读超时是关键,否则读写器断电时程序会永久阻塞。inventory 方法用一个 1 秒的时间窗口采集,因为 RFID 读取是持续的,你不知道什么时候「读完」,只能按时间截断。参数上,soTimeout 设 2 秒要大于单次读取间隔,否则会误判为结束。parseEpcs 需要按厂商协议解析,通常是「帧头 + 长度 + 命令字 + EPC数据 + 校验」,校验方式常见的有 Checksum 和 CRC16。

注意:不同厂商的指令格式完全不通用。拿到读写器第一件事是找它的通信协议手册,把「盘存命令」和「响应帧格式」两节看懂,比抄代码有用得多。

3.2 EPC 编码规则和资产编号怎么映射

读写器读到的 EPC 是一串十六进制,人看不懂。后台需要把它和资产编号对应起来。有两种做法:一种是把资产编号直接写进标签的用户区(User Bank),读的时候一起读出来;另一种是维护一张 EPC 到 asset_code 的映射表,就是前面 t_rfid_tag 干的事。毕设推荐第二种,因为不用额外操作标签写入,绑定关系在后台改就行。

方案优点缺点适用场景
EPC 映射表实现简单,改绑方便多一次数据库查询资产数量万级以内
写入用户区离线也能识别需要读写器支持写操作,写错难改资产流动性强的场景
EPC 直接编码资产号无需查库EPC 长度受限,编码规则死板资产编号短的场景

实际项目里我一般用映射表,因为盘点时是先批量读 EPC 再统一查库,一次select ... where tag_epc in (...)就搞定,不会有性能问题。

4. 避坑与排查:这套源码跑不起来时先看这几条

从网上下的源码,十个里有八个不能直接跑。下面是我踩过的坑,按「现象 → 原因 → 解决」列出来,照着排查能省不少时间。

现象一:项目启动报Table 'xxx' doesn't exist。原因:源码里只有 Java 代码,没有建表 SQL,或者 SQL 文件藏在 doc 目录里没注意。 解决:先在项目里搜.sql文件,找不到就根据 entity 类的字段反推建表语句。注意字段名驼峰转下划线的规则,MyBatis 默认开启map-underscore-to-camel-case时,assetCode对应asset_code。

现象二:读写器连上了但读不到任何标签。原因:天线没接、功率设太低、标签类型不匹配(高频读写器读不了超高频标签)。 解决:先用厂商自带的演示软件测试,确认硬件本身能读。能读之后再回来调 Java 代码。功率从 20dBm 开始往上加,每次加 2dBm。高频 13.56MHz 和超高频 860-960MHz 是两套完全不同的硬件,别搞混。

现象三:盘点结果里同一件资产出现多次。原因:读写器在标签停留期间反复上报同一个 EPC,代码没去重。 解决:接收端用 Set 去重,或者在 SQL 查询时用DISTINCT。更彻底的做法是在采集窗口内对同一 EPC 做计数,超过阈值的才认为是有效读取。

现象四:中文资产名称存进数据库变成问号。原因:数据库连接 URL 没指定字符集,或者表创建时用了 latin1。 解决:JDBC URL 加?useUnicode=true&characterEncoding=utf8,建表统一用utf8mb4。已经建错的表用ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4改。

现象五:并发领用同一件资产,两个人都成功了。原因:查询资产状态和更新状态之间没有加锁,典型的竞态条件。 解决:在 service 方法上加@Transactional,更新时用乐观锁update t_asset set status=1 where id=? and status=0,根据返回的影响行数判断是否成功。返回 0 就说明被别人抢先了。

5. 让答辩加分:把盘点差异做成可追溯的闭环

前面讲的都是「能跑」,这一章讲「跑得好」。答辩时老师最爱问的一句话是:「你这个盘亏的资产,怎么证明是真的丢了,而不是你系统读漏了?」如果你只能回答「重新盘一次」,那就显得很单薄。正确的做法是把每次盘点的原始数据、比对结果、人工确认动作全部留痕,形成一个可追溯的闭环。

具体做法是加一张盘点任务表和一张盘点明细表。任务表记录「谁在什么时候盘了哪个部门」,明细表记录「这次盘点中每个 EPC 的读取次数和最终判定」。读取次数这个字段很关键:读一次可能是误读,读十次基本可以确认标签就在现场。判定结果分「正常、盘亏、盘盈、待确认」四种,盘亏和盘盈必须人工点确认才能关闭任务。

CREATE TABLE t_inventory_task ( id BIGINT PRIMARY KEY AUTO_INCREMENT, task_name VARCHAR(128), dept_id BIGINT, operator VARCHAR(32), status TINYINT DEFAULT 0 COMMENT '0进行中 1已完成', start_time DATETIME, end_time DATETIME ); CREATE TABLE t_inventory_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, task_id BIGINT NOT NULL, tag_epc VARCHAR(96), asset_id BIGINT, read_count INT DEFAULT 0 COMMENT '本次盘点读取次数', judge_result TINYINT COMMENT '0正常 1盘亏 2盘盈 3待确认', KEY idx_task (task_id) );

有了这两张表,老师再问「读漏了怎么办」,你可以说:读取次数低于 3 次的标记为待确认,需要人工拿着手持机到现场复核,复核结果也写进明细表。这样整个盘点过程就是可审计的,而不是拍脑袋。

进阶一点,可以做一个简单的读取次数分布统计,用 Java 的 Stream 按 read_count 分桶,看看大部分标签的读取次数集中在哪个区间。如果某个标签只读了 1 次而其他都是 10 次以上,那它大概率是边缘误读,系统可以自动把它标成待确认而不是直接判盘亏。这个逻辑不复杂,但答辩时讲出来,说明你思考过真实场景,比单纯堆功能强得多。

// 按读取次数分桶,辅助判断哪些是可疑读取 Map<String, Long> bucket = details.stream() .collect(Collectors.groupingBy( d -> { if (d.getReadCount() >= 10) return "高置信"; if (d.getReadCount() >= 3) return "中置信"; return "低置信"; }, Collectors.counting() )); // 低置信的自动转待确认,不直接判盘亏 details.stream() .filter(d -> d.getReadCount() < 3) .forEach(d -> d.setJudgeResult(3));

我自己做这类系统最大的习惯是:任何「自动判定」都要留一个人工复核的出口。RFID 再准也有物理极限,金属和液体旁边的标签读取率会明显下降,这不是代码能解决的。把不确定性显式地暴露给用户,比假装系统百分百准确要靠谱得多。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询