news 2026/9/8 6:25:31

Java固定资产管理系统源码解析:Spring Boot+MyBatis-Plus实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java固定资产管理系统源码解析:Spring Boot+MyBatis-Plus实战

简介:基于Java、若依框架与layui的固定资产管理源码包,面向Java开发人员、若依学习者以及需要快速搭建资产管理系统的开发者。系统覆盖资产登记、领用、借用、归还、维修、调拨、转移、报废和统计等全流程,并内置组织结构管理与角色权限分配,可帮助理解企业级权限设计与资产流转逻辑。压缩包共1883个文件,以Java源码(317个)、HTML页面、JavaScript、CSS、XML配置文件为主,包含较完整的前后端代码及资源,包体8.88MB,轻量且便于直接导入IDE运行或二次开发。已有552人学习,适合毕业设计、课程项目或实际业务改造时参考;若依框架的权限控制、layui交互界面以及资产领用、调拨等核心模块均可对照源码逐一研读,便于快速理解整体实现。 做企业管理类项目时,固定资产管理系统算是被问得最多的“小系统”之一。表面上无非是登记一下设备、贴个标签、记录领用归还,但真动手写源码之后,你会发现资产状态流转、盘点差异、批量导入、权限控制这些点,每个都能写成一篇独立的踩坑记录。这篇基于Java的固定资产管理源码,核心就是围绕资产的完整生命周期——入库、领用、调拨、维修、报废、盘点——把所有业务操作串起来,用一套中小企业也能直接落地的方案把它实现。

这套系统的定位很清晰:不搞复杂的分布式架构,不堆微服务,就是用 Spring Boot + MyBatis-Plus + MySQL 这套主流组合,把资产管理的常用场景做扎实。适合三类人看:一是打算做企业管理系统的 Java 后端开发,二是需要完成毕业设计或课程设计的学生,三是准备接企业信息化外包、想快速交付一套完整模块的开发者。下面我会从业务设计、核心模块、关键代码到排障经验,把整套源码的落地过程完整拆开讲。

1. 项目整体设计与技术选型思路

1.1 资产管理到底在管什么

先把业务边界划清楚。固定资产管理常见的模块有这几块:

  1. 资产登记和卡片管理:把采购来的设备变成一条条“资产卡片”,包含资产编号、名称、型号、金额、使用部门、存放地点等信息。
  2. 领用与归还:员工借用笔记本电脑、会议室投影仪等都要留有记录,责任到人。
  3. 调拨转移:资产从 A 部门调到 B 部门,经手人和时间必须可追溯。
  4. 维修保养:设备坏了要申请维修,记录维修状态和费用,设备是否处于可用状态一目了然。
  5. 报废处置:无法继续使用的资产走报废审批流程,退出使用流通。
  6. 盘点:定期核对账面资产和实际实物是否一致,盘盈盘亏都需要处理。
  7. 折旧计算:财务侧关注资产净值,非财务系统可以简化,用平均年限法就能满足绝大多数场景。

很多新手容易犯的错是,把系统直接设计成一张大表,所有字段全塞进去,状态也用一个大字段糊弄。数据量一多、流程一复杂,SQL 就没法写,业务判断也乱成一团。正确做法是先抽象出几条核心业务线,再围绕每条线设计表和接口,让业务逻辑和数据结构一一对应。

1.2 技术栈选型逻辑

这套源码里我用了当前 Java 后端最主流的一套组合:

  • 后端:Spring Boot 2.x + Spring MVC
  • 持久层:MyBatis-Plus(比原生 MyBatis 少写大量 XML)
  • 数据库:MySQL 8.x + Redis(做缓存和部分并发控制)
  • 安全框架:Spring Security(做登录认证和角色权限)
  • 前端:Vue 2 + Element UI(便于快速搭出后台管理界面)
  • 工具库:EasyExcel(资产导入导出)、Hutool(通用工具集)

为什么这么选?中小企业项目的核心诉求是“开发快、后期有人能维护”。Spring Boot 解决了配置地狱问题,MyBatis-Plus 让单表 CRUD 基本不用写 SQL,EasyExcel 处理几百上千条资产数据的导入导出也不会内存溢出。这套组合不够复杂、不够炫技,但它足够稳,也足够让接手的人快速看懂。

如果只是做个人毕设,用更轻的 Sa-Token 替换 Spring Security 也行,登录鉴权逻辑能省下一多半。但考虑到企业项目普遍用 Spring Security,看这套源码的读者以后进项目组会更熟悉,所以还是保留它。

提示:技术栈不是越新越好,而是看团队熟悉度和维护成本。一个人维护系统时,Lombok、Hutool 这类工具库能帮你节省大量时间。

1.3 项目结构和角色权限设计

先放出后端包结构,按业务模块分包而不是按技术分包,后续扩展会舒服很多:

com.capital.asset ├── common # 通用工具、统一返回、异常处理 ├── config # Spring Boot 配置类 ├── security # 登录认证、权限注解 ├── module │ ├── asset # 资产卡片模块 │ ├── category # 资产分类模块 │ ├── borrow # 领用/归还模块 │ ├── repair # 维修模块 │ ├── scrap # 报废模块 │ ├── inventory # 盘点模块 │ └── report # 报表统计模块 ├── entity # 数据库实体 ├── mapper # MyBatis-Plus Mapper └── service # 业务逻辑层

角色我设计了三种:系统管理员、资产管理员、普通员工。

  • 系统管理员:管用户、角色、菜单,一般不动业务数据。
  • 资产管理员:核心业务操作者,负责录入资产、审批领用、发起盘点。
  • 普通员工:只能查询和提交申请,不能直接改资产状态。

权限用注解控制,比如资产新增需要@PreAuthorize("hasRole('ADMIN') or hasRole('ASSET_ADMIN')")。这样写业务代码时职责非常清楚,权限调整也不需要动核心逻辑,只改数据库里的角色权限关联即可。

2. 核心业务模块与技术细节拆解

2.1 资产卡片表结构设计

资产管理系统的灵魂是“资产卡片表”。字段设计直接决定后面所有模块的复杂度。我给出一个已经跑过真实项目的核心表结构(简版):

CREATE TABLE `asset_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `asset_no` varchar(64) NOT NULL COMMENT '资产编号(唯一)', `asset_name` varchar(255) NOT NULL COMMENT '资产名称', `category_id` bigint(20) DEFAULT NULL COMMENT '分类ID', `model` varchar(255) DEFAULT NULL COMMENT '规格型号', `buy_date` date DEFAULT NULL COMMENT '购置日期', `buy_price` decimal(12,2) DEFAULT NULL COMMENT '购置金额', `department_id` bigint(20) DEFAULT NULL COMMENT '使用部门ID', `user_id` bigint(20) DEFAULT NULL COMMENT '当前使用人ID', `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态:1在库 2已领用 3维修中 4已报废 5盘点中', `location` varchar(255) DEFAULT NULL COMMENT '存放地点', `version` int(11) NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除:0正常 1删除', PRIMARY KEY (`id`), UNIQUE KEY `uk_asset_no` (`asset_no`), KEY `idx_status` (`status`), KEY `idx_department` (`department_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资产信息表';

这张表有几点值得说明:

  • asset_no 字段加了唯一索引,这是硬约束,保证资产编号不可能重复,比在业务代码里先查再插入靠谱一百倍。并发情况下靠代码判断编号是否存在,依然会出现重复,唯一索引是最后一层保障。
  • status 字段用 tinyint 而不是 varchar。状态是可穷举的固定集合,用数字既省空间又避免乱七八糟的写法。
  • version 字段是给乐观锁用的,后面处理并发领用时,没有它就会出大问题。
  • deleted 字段是逻辑删除。企业系统宁可多保留数据也不要做物理删除,以后审计、追踪还有用。

领用、维修、报废这些业务表,都统一带上asset_idoperator_idoperate_time和备注字段,这样每一条资产的历史链路都能串起来。

2.2 资产状态机设计

资产状态不是随便改的,每一步操作都会改变状态,而且有严格的流转方向:

在库(1) → 已领用(2) → 在库(1) # 领用与归还 在库(1) → 维修中(3) → 在库(1) # 维修流程 已领用(2) → 维修中(3) → 已领用(2) # 领用期间设备维修 在库(1) → 已报废(4) # 报废直接终结

这一设计落在 Service 层就是前置状态校验。比如提交领用申请时,只允许状态为“在库”的资产被领用;如果员工已经拿走某台设备,别人再申请同一台,就应该直接拒绝。代码可以统一走一个状态校验方法:

private void checkStatus(AssetInfo asset, Integer expectStatus) { if (!expectStatus.equals(asset.getStatus())) { throw new BizException("资产[" + asset.getAssetNo() + "]当前状态不允许该操作"); } }

这个方法是所有资产操作入口的第一道闸门。别小看它,很多线上 Bug 就是少了这行校验,比如已报废资产被再次领用、已领用资产被重复登记维修。

2.3 前端菜单与后端的权限联动

权限设计不是只在后端写几个注解就完了。前端也需要根据当前用户的角色动态生成菜单和按钮,否则普通员工也能看到“新增资产”按钮。

实操中我在后端登录接口里返回一个permissions列表,前端根据这个列表来控制按钮隐藏:

{ "userId": 1, "userName": "admin", "roles": ["ADMIN"], "permissions": [ "asset:add", "asset:edit", "asset:delete", "inventory:start", "report:view" ] }

菜单路由在前端用v-if="perms.includes('asset:add')"控制。这样做的另一个好处是,如果后续要调整某个岗位的权限,只需要改数据库里的角色权限关联数据,前端无需重新发版。

3. 关键功能模块的实现流程与核心代码

3.1 资产批量导入的两种实现

资产录入是第一步,但逐条在表单里填太慢了。真实场景下,管理员往往是拿到一张 Excel 表格,直接导入系统。这里我用 EasyExcel 来实现,对比传统 POI,它的最大优势是解析时逐行回调,不会一次性把整个大文件加载进内存。

核心代码大致是这样:

public void importAsset(MultipartFile file) { EasyExcel.read(file.getInputStream(), AssetImportDTO.class, new AnalysisEventListener<AssetImportDTO>() { private final List<AssetInfo> list = new ArrayList<>(); @Override public void invoke(AssetImportDTO dto, AnalysisContext context) { AssetInfo asset = new AssetInfo(); asset.setAssetNo(dto.getAssetNo()); asset.setAssetName(dto.getAssetName()); asset.setBuyDate(DateUtil.parse(dto.getBuyDate())); asset.setStatus(1); list.add(asset); // 每攒够100条批量插入一次,减少数据库交互次数 if (list.size() >= 100) { assetService.saveBatch(list); list.clear(); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { if (!list.isEmpty()) { assetService.saveBatch(list); } } }).sheet().doRead(); }

踩坑提示:

  • 导入模板必须固定列顺序,DTO 里字段用@ExcelProperty(index = X)锁定,不要只靠字段名匹配,否则 Excel 列顺序一改就崩。
  • 编号重复校验分两层:先查询数据库已有编号集合进行内存判重,再靠库里的唯一索引兜底。文件内部重复也一样处理。
  • 日期格式是重灾区,Excel 里的“2024/5/8”和“2024-05-08”解析结果不同,建议统一先转成字符串,再按规范格式解析。

3.2 领用和归还的事务处理

领用操作表面上只是把资产的user_id改一下、状态改成“已领用”,但这里牵扯三个动作:更新资产表、插入领用记录、扣减部门可用额度(如果有类似统计)。任何一步失败都需要整体回滚,所以必须加事务:

@Transactional(rollbackFor = Exception.class) public void borrowAsset(BorrowRequest req) { LambdaUpdateWrapper<AssetInfo> wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(AssetInfo::getId, req.getAssetId()) .eq(AssetInfo::getStatus, 1) // 只有"在库"才能领用 .eq(AssetInfo::getVersion, req.getVersion()); // 乐观锁 AssetInfo update = new AssetInfo(); update.setUserId(req.getUserId()); update.setStatus(2); int rows = assetMapper.update(update, wrapper); if (rows == 0) { throw new BusinessException("资产已被领用或状态变更,请刷新后重试"); } BorrowRecord record = new BorrowRecord(); record.setAssetId(req.getAssetId()); record.setUserId(req.getUserId()); record.setBorrowTime(LocalDateTime.now()); borrowRecordMapper.insert(record); }

这个写法的关键点在updatewhere条件里同时带上了status = 1version = 旧版本号。两个并发用户同时点“领用”时,会去更新同一条记录,但 MySQL 的UPDATE语句是行锁串行执行的,后一个事务执行时,资产状态已经变成 2,version 也已经 +1,所以匹配不到记录,更新行数为 0,直接抛异常。

这套思路比“先查询再判断”优雅得多,既不用手动加锁,又能百分百避免并发超领。领用记录插入与资产更新在同一个事务里,异常时会整体回滚,不会出现资产状态已变但记录缺失的脏数据。

3.3 盘点任务的生成与差异处理

盘点最怕的是“账面一套、实物一套”。常规做法是:管理员创建盘点任务,系统把所有在册资产捞出来生成盘点明细列表;盘点员在电脑或手机上对逐条资产确认“存在、缺失、损坏”;结束后生成差异报表。

这里我分享一个差异统计的小技巧。盘点结果表用一张表存两个状态:账面状态book_status和实盘状态actual_status。盘点结束后,一条 SQL 就能统计出所有差异:

SELECT SUM(CASE WHEN book_status = 1 AND actual_status = 2 THEN 1 ELSE 0 END) AS lost_count, SUM(CASE WHEN book_status = 2 AND actual_status = 1 THEN 1 ELSE 0 END) AS surplus_count FROM inventory_detail WHERE inventory_id = #{inventoryId};

book_status=1 且 actual_status=2,表示账面在库但实际被领用,属于“盘盈”;book_status=2 且 actual_status=1,表示账面上被人领走但实物没找到,属于“盘亏”。这类统计 SQL 写好了,比在 Java 内存里循环判断高效得多。

3.4 折旧计算与报表统计

如果老板问“公司固定资产现在净值还有多少”,你不可能一张张算。我在系统里用了最简单的平均年限法,公式是:

月折旧额 = (资产原值 - 残值率) / 使用年限 / 12 月净值 = 资产原值 - 月折旧额 × 已计提月数

考虑到企业实际业务中,很多设备可能中途改造、维修增加价值,真正成熟的折旧引擎会复杂很多。但作为一套通用的固定资产管理源码,平均年限法已经能覆盖大部分中小企业的需求。

报表接口我用一条带条件的分组 SQL 完成,比如按部门统计资产总值:

SELECT d.dept_name, COUNT(a.id) AS asset_count, SUM(a.buy_price) AS total_price FROM asset_info a LEFT JOIN department d ON a.department_id = d.id WHERE a.deleted = 0 GROUP BY a.department_id ORDER BY total_price DESC;

把查询结果丢给前端 ECharts 直接画饼状图或柱状图即可。如果还涉及财务口径,再单独加一张asset_value_log表,记录每月的资产净值快照。

4. 常见问题与排查技巧实录

4.1 资产编号重复导致的导入失败

现象:批量导入时,Excel 里有好几行使用同一个编号,但第一行导入成功,后面几行全部报唯一索引冲突。

排查思路:代码里虽然做了内存判重,但判重逻辑如果只对比了“数据库已有编号”,没对比“Excel 文件内部的编号”,文件内部重复就会被漏掉。

解决方式:读 Excel 时维护一个HashSet,每次读到编号先判断文件内是否已存在;同时每条数据入库前再查一次数据库编号集合。两级去重才可靠。

4.2 并发操作下资产状态错乱

现象:A 员工把资产领用了,B 同时把同一台资产提交维修,最后系统里既生成了领用记录,又生成了维修记录,资产状态变成“维修中”,但实物已经被 A 拿走使用。

根因:两个操作都没有做前置状态校验,也没有用乐观锁。前面 3.2 节代码的意义就在这。

排查建议:在 Service 层所有修改资产状态的方法里打印操作日志,内容包括操作人、操作时间、操作前后状态,方便线上追踪哪一步覆盖了别人状态。

4.3 MySQL 时区问题导致时间差 8 小时

现象:服务器上部署后,新增资产的时间比本地时间慢了 8 小时,数据库里存的是 UTC 时间。

原因和解决:

spring: datasource: url: jdbc:mysql://localhost:3306/asset_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8 jackson: time-zone: GMT+8

同时确保 MySQL 连接参数里加上serverTimezone=Asia/Shanghai,并且 JVM 启动参数设置为-Duser.timezone=GMT+8,或者在程序启动类里统一设置时区。这一条要注意:很多本机开发正常、上服务器就不正常的案例,十有八九是时区配置不一致。

4.4 Redis 缓存资产列表时序列化报错

现象:启动报java.lang.ClassCastException,或者缓存里存进去的对象读出来变成了乱码。

原因:没有配置 Redis 的 Json 序列化器,默认用的 JDK 序列化,导致跨语言、跨环境读取不了。

解决:自定义一个RedisTemplate,value 序列化器换成Jackson2JsonRedisSerializer,key 用StringRedisSerializer。配置代码不算复杂,但值得提前写好:

@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); StringRedisSerializer stringSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); jacksonSerializer.setObjectMapper(om); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(jacksonSerializer); template.setHashValueSerializer(jacksonSerializer); template.afterPropertiesSet(); return template; }

写进项目里后,再遇到“缓存的数据读出来类型不对”的概率会低很多。

5. 部署与后续扩展建议

5.1 快速部署流程

这套系统是前后端分离的,部署时可以按下面步骤走:

  1. 后端用mvn clean package -DskipTests打成 jar 包。
  2. 服务器上安装 JDK 8+ 和 MySQL,并初始化数据库脚本。
  3. nohup java -jar asset-management.jar --spring.profiles.active=prod > app.log 2>&1 &启动后端。
  4. 前端打包后把 dist 目录放到 Nginx 的 html 目录下,并配置反向代理转发/api请求到后端端口。

如果服务器内存不大,启动参数可以加上-Xms256m -Xmx512m,一个中小型公司几百人的资产系统,这个规格完全够用。

5.2 功能扩展方向

这套源码后续还可以这样扩展,优先级从高到低排列:

扩展方向技术要点适用场景
二维码资产标签打印资产二维码,手机扫码跳转资产详情页盘点效率提升
移动端盘点基于微信小程序或 H5 做盘点页面仓库、多楼层企业
对接企业微信或钉钉审批用回调接口同步审批结果已有 OA 体系的公司
财务折旧引擎引入年限、残值率、改扩建影响财务对账有硬需求
资产预算提醒每年指定预算,超预算自动预警集团型公司

我个人建议扩展前先确认业务方真实需求,不要一上来就把系统做重。很多公司只需要“把资产管住”,固定资产管理的核心永远是“账实一致”,功能和报表是围绕这个目标服务的。

最后分享一个我自己做这类项目时的小体会。固定资产管理系统看起来是典型的 CRUD 项目,技术难度不大,但它非常考验对业务细节的理解和对边界情况的处理。领用时的并发控制、导入时的去重校验、状态流转时的前置判断,这些才是源码里真正值钱的东西。如果你打算用这套思路自己写一套,建议先把表结构设计和状态机画清楚,代码反而是后面顺理成章的事情。拿去接项目或做毕设,只要把资产卡片、领用归还、盘点、报表这几条主线做完整,再补齐权限和日志,整个系统的完成度就已经很高了。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 6:24:48

C++实现数据结构与算法:从链表到红黑树(源码+图解)

一、为什么要用 C 手写数据结构很多开发者在刷题、面试或做底层系统开发时都会遇到一个共同问题&#xff1a;标准库容器用起来很顺手&#xff0c;但一旦被问到「底层是怎么实现的」&#xff0c;比如 std::map 为什么查找是 O(log n)、std::list 和 std::vector 的插入删除差异在…

作者头像 李华
网站建设 2026/9/8 6:24:35

3DMAX次世代建模教程:从Box到药水瓶的卡线与多边形布线全流程

先别急着下载那些几百 MB 的“次世代模型资源包”。这次我们来看一个非常基础、但被很多人低估的 3DMAX 建模思路&#xff1a;从一个 box 开始&#xff0c;手动搭建出一个次世代品质的药水瓶。这个项目的核心不是复杂的插件&#xff0c;也不是高配显卡&#xff0c;而是你对“可…

作者头像 李华
网站建设 2026/9/8 6:24:24

SolidWorks工程图出图规范:从视图标注到DWG交付的完整流程

上个月帮朋友审一套 SolidWorks 出图的变速箱壳体图纸&#xff0c;打开工程图文件后我愣了几秒&#xff1a;三个主视图叠在一起&#xff0c;尺寸标注有的靠模型项目自动生成&#xff0c;有的手拖&#xff0c;公差一个都没标&#xff0c;技术要求只有“未注圆角R0.5”一行字&…

作者头像 李华
网站建设 2026/9/8 6:24:21

2026年普通人AI工具箱配置指南:五大场景少而精

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 6:24:08

二叉树遍历序列判断:先序入栈中序出栈,秒解“不可能”中序

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 6:23:35

STM32控制TT马达从入门到排障:PWM调速与H桥驱动详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华