固定资产管理系统Java源码解析:基于SSM框架的完整资产全生命周期实现
简介固定资产管理系统Java项目源码以完整源代码形式提供了一套基于Java技术开发的资产管理系统覆盖固定资产的增、删、改、查、采购入库、分配折旧、盘点报废等生命周期管理功能并支持通过ODBC配置灵活切换SQL Server、MySQL、Oracle等数据库具备良好的跨平台与扩展性适合Java Web开发者学习数据访问层与业务逻辑设计也可作为企业资产管理系统的定制模板。压缩包共606个文件、约8.73MB其中包含131个Java源文件、177个编译后的class文件、59个JSP页面以及42个jar依赖库和多个tld、dtd、xml配置文件源码与页面分层清晰便于按模块阅读和二次开发。包内附带的“必看.pptx”介绍了系统架构、功能模块、数据库表结构及ODBC连接配置步骤可帮助快速理解运行机制并上手实践。已有1686人学习下载是从代码层面掌握固定资产全流程管理、提升Java Web综合开发能力的实用资源。1. 固定资产管理系统一个 Java 项目源码包解决的不只是台账问题如果你在找资产管理系统大概率是两种处境之一公司 Excel 台账越记越乱资产标签贴了又丢年终盘点全靠人肉对账或者你是个开发者想找一个完整的 Java 项目来改造却总在「代码太老跑不起来」和「功能太简单没参考价值」之间来回横跳。这个固定资产管理系统源码包属于后者里难得让我愿意多看几眼的它不只是一个能增删改查的 CRUD 架子而是把资产从入库、领用、归还、调拨到盘点、报废的完整生命周期都串了起来并且带了权限控制和报表导出。适合两类人一类是有 Java Web 基础、想拿真实业务练手的学生或初级开发另一类是真想给内部做个资产管理后台、但不想从零开始的中小企业技术负责人。我拆过不少这类项目很多号称「管理系统」的源码其实只是把几个页面拼在一起点开一看业务逻辑薄得像层纸。这份源码的厚度不太一样资产编号生成规则、领用审批流程、盘点差异处理这些细节都有落地实现。接下来我会从架构、核心代码、踩坑记录到二次开发把这份源码从头到尾捋一遍你可以边看边对照自己手头的需求看看哪些模块能直接拿来用。2. 技术选型与项目结构一个典型 Java Web 工程的骨架长什么样2.1 后端技术栈为什么选 SSM 而不是 Spring Boot这个项目的后端是基于 SSM 框架搭建的也就是 Spring Spring MVC MyBatis 三件套的组合构建工具用的是 Maven数据库是 MySQL前端是 JSP JSTL 配合 AdminLTE 这套开源后台模板。可能你会问现在新项目不都用 Spring Boot 了吗为什么还要看 SSM我的判断是这正是这份源码的价值所在——它不像 Spring Boot 那样把大量配置自动隐藏了而是把数据源、事务、拦截器、MyBatis 映射这些底层细节都显式地暴露在 XML 和配置类里。你拿到手之后能清楚地看到每一个 Bean 是怎么装配起来的MyBatis 的 Mapper 是怎么和 DAO 层关联的这对于理解 Java Web 项目的运行机制比直接用 Boot 更有效。另外从改造成本来看SSM 项目迁移到 Spring Boot 其实不复杂核心业务代码基本不用动只要把 Spring 和 MyBatis 的配置换成 Boot 的自动配置再调整一下依赖就行。所以不要因为技术栈不是最新的就把它划走这套源码的架构思路是通用的你把它吃透了换个框架只是顺手的事。2.2 目录结构从 controller 到 mapper 的完整分层解压源码包之后你会看到典型的 Maven 工程结构。我建议你先不要急着去看代码先把目录捋一遍搞清楚每个包是干什么的这样后面读代码的时候心里有数。src/main/java ├── com.company.asset │ ├── controller // 控制层接收请求、参数校验、返回视图 │ │ ├── AssetController.java │ │ ├── CategoryController.java │ │ ├── DeptController.java │ │ ├── UserController.java │ │ ├── BorrowController.java │ │ ├── ScrapController.java │ │ ├── CheckController.java │ │ └── ReportController.java │ ├── service // 业务层事务边界、业务规则 │ │ ├── AssetService.java │ │ ├── AssetServiceImpl.java │ │ ├── UserService.java │ │ └── ... │ ├── dao // 数据访问层MyBatis mapper 接口 │ │ ├── AssetDao.java │ │ ├── CategoryDao.java │ │ ├── DepartmentDao.java │ │ ├── UserDao.java │ │ └── ... │ ├── entity // 实体类对应数据库表 │ │ ├── Asset.java │ │ ├── Category.java │ │ ├── Department.java │ │ ├── User.java │ │ ├── Borrow.java │ │ └── ... │ ├── vo // 视图对象承载组合查询结果 │ │ ├── AssetQueryVO.java │ │ └── ReportVO.java │ ├── interceptor // 拦截器登录校验、权限控制 │ │ └── AuthInterceptor.java │ ├── util // 工具类编号生成、Excel 导出 │ │ ├── AssetCodeGenerator.java │ │ └── ExcelUtil.java │ └── config // 配置类 │ └── SpringMVCConfig.java ├── resources │ ├── spring-context.xml │ ├── spring-mvc.xml │ ├── mybatis-config.xml │ ├── jdbc.properties │ └── mapper │ ├── AssetMapper.xml │ ├── CategoryMapper.xml │ └── ... └── webapp ├── WEB-INF │ ├── views │ │ ├── asset │ │ ├── borrow │ │ ├── check │ │ ├── report │ │ └── system │ └── web.xml └── static ├── adminlte ├── css ├── js └── images这个分层是教科书式的经典结构Controller 只负责接收参数和返回视图业务逻辑全部收敛在 Service 层DAO 层只做数据读写。我当时看代码的时候特别注意了一下事务注解发现Transactional是放在 ServiceImpl 类上的这符合 Spring 官方推荐的做法——事务边界应该定义在业务逻辑层而不是控制层。多层结构带来的一个好处是如果你想把 JSP 换成 Vue 接口模式只需要改 Controller 层的返回逻辑把 ModelAndView 改成ResponseBody返回 JSONService 和 DAO 层可以完全复用。数据库脚本在sql目录下一共 8 张核心表t_asset资产主表、t_category分类表、t_department部门表、t_user用户表、t_borrow领用/归还记录表、t_borrow_item领用明细表、t_check盘点记录表、t_scrap报废记录表。表之间的关联关系是资产属于某个分类和某个部门领用记录关联资产和用户盘点记录关联资产和盘点结果。字段设计的细节我在后面核心模块里展开讲。3. 核心模块拆解资产从入库到报废的完整生命周期3.1 资产台账模块编号自动生成与组合查询资产台账是系统的数据底座所有其他业务都围绕着资产主表展开。先看资产实体类和它的 Mapper 实现重点理解两个地方资产编号怎么生成、组合查询怎么拼 SQL。// Asset.java 实体类关键字段 public class Asset { private Integer id; // 主键 private String assetCode; // 资产编号如 ZC-2024-0001 private String assetName; // 资产名称 private Integer categoryId; // 分类ID private Integer deptId; // 使用部门ID private String status; // 状态在库/领用/维修/报废 private Date purchaseDate; // 购置日期 private BigDecimal price; // 原值 private Integer userId; // 当前保管人 private String remark; // 备注 // getter/setter 省略 }!-- AssetMapper.xml 组合查询部分 -- select idqueryAssetList resultTypecom.company.asset.vo.AssetQueryVO SELECT a.*, c.name AS categoryName, d.name AS deptName, u.real_name AS userName FROM t_asset a LEFT JOIN t_category c ON a.category_id c.id LEFT JOIN t_department d ON a.dept_id d.id LEFT JOIN t_user u ON a.user_id u.id where if testassetName ! null and assetName ! AND a.asset_name LIKE CONCAT(%, #{assetName}, %) /if if testcategoryId ! null AND a.category_id #{categoryId} /if if testdeptId ! null AND a.dept_id #{deptId} /if if teststatus ! null and status ! AND a.status #{status} /if /where ORDER BY a.id DESC /select这个组合查询是资产管理里最常见的需求。四个条件全部用if动态拼接判断条件是否为空再决定要不要拼进 SQL这样前端传什么条件就过滤什么条件不传就是全量查询。需要注意order by放在where外面排序不会因为动态条件受影响。这里的三表联查返回的是AssetQueryVO就是为了在前端列表页直接展示分类名、部门名和保管人姓名避免在 Java 代码里做二次查询。资产编号生成是另一个值得看的点。// AssetCodeGenerator.java public class AssetCodeGenerator { // 根据当前日期当天流水号生成编号例如 ZC-20241015-0001 public static String generate(String prefix, int seq) { SimpleDateFormat sdf new SimpleDateFormat(yyyyMMdd); return String.format(%s-%s-%04d, prefix, sdf.format(new Date()), seq); } }这段代码逻辑很直观传入前缀资产用 ZC领用单用 LY拼上日期和四位数流水号。实际生产环境里可以在 Service 层先查询当天已有的最大编号再 1 传给generate方法保证同一天内编号递增、跨天自动归零。如果系统要支持多公司或多仓库可以在前缀里再加一段机构编码改造成本很低。3.2 领用与归还模块状态机的流转设计资产领用是整个系统里业务逻辑最完整的流程。公司内部的资产借用不是简单地改一下asset.status就完事了需要记录领用人、领用时间、预计归还时间归还的时候还要校验资产状态和保管人是否匹配。这个模块我拆开看发现它内部是一个典型的状态机驱动。// BorrowServiceImpl.java 归还逻辑 Override Transactional public void returnAsset(Integer borrowItemId) { // 1. 查询领用明细记录 BorrowItem item borrowItemDao.findById(borrowItemId); if (item null) { throw new BusinessException(领用记录不存在); } // 2. 判断记录状态必须处于领用中才能归还 if (!2.equals(item.getStatus())) { throw new BusinessException(当前记录状态不可归还); } // 3. 更新明细状态为已归还 item.setStatus(3); // 3已归还 item.setReturnTime(new Date()); borrowItemDao.update(item); // 4. 更新资产主表状态为在库 Asset asset assetDao.findById(item.getAssetId()); asset.setStatus(1); // 1在库 asset.setUserId(null); assetDao.update(asset); }这个方法的精妙之处在于状态校验前置。它不是上来就改数据而是先做两件关键的事查记录是否存在、查记录状态是否允许归还。这个「状态不匹配就不执行」的思路是这套代码的典型风格你要是在做二次开发最好也保持这个习惯不要在 Service 里跳过状态判断直接改库不然盘点数据会乱到怀疑人生。领用的时候逻辑类似区别在于领用是「创建一条领用单 更新资产状态」两步操作得益于Transactional注解这两步要么都成功要么都失败不会出现领用单建了但资产状态没改的情况。状态值设计上用了数字字符串1 在库、2 领用中、3 维修、4 报废这是老项目的常见做法优点是好比较缺点是语义不够清晰。你要是想改造可以把数字映射成枚举类但要注意前端 JSP 里可能写死了状态值改的时候前后端要一起动。3.3 盘点模块差异记录怎么处理固定资产年度盘点是最容易出幺蛾子的环节账实不符是家常便饭。盘点模块的处理逻辑是管理员发起盘点任务生成一张盘点单然后逐条勾选资产填写实盘结果。如果实盘数量与账面数量不一致系统自动生成差异记录并把差异原因记录下来。// CheckServiceImpl.java 盘点差异处理 public CheckResult generateDiffReport(Integer checkId) { // 1. 查出该盘点单下所有明细与资产账面状态 ListCheckItem items checkItemDao.findByCheckId(checkId); ListDiffItem diffList new ArrayList(); for (CheckItem item : items) { Asset asset assetDao.findById(item.getAssetId()); // 2. 对比账面状态与实盘状态 boolean statusDiff !asset.getStatus().equals(item.getStatus()); boolean deptDiff !asset.getDeptId().equals(item.getDeptId()); if (statusDiff || deptDiff) { DiffItem diff new DiffItem(); diff.setAssetId(item.getAssetId()); diff.setCheckId(checkId); diff.setBookStatus(asset.getStatus()); diff.setRealStatus(item.getStatus()); diff.setBookDept(asset.getDeptId()); diff.setRealDept(item.getDeptId()); diff.setHandleStatus(0); // 0待处理 1已处理 diffDao.insert(diff); } } // 3. 汇总差异结果 return buildCheckResult(checkId, diffList); }这段代码的逻辑是把账面数据和盘点数据逐一对比发现差异就生成一条待处理记录等于把「发现什么问题」和「怎么处理问题」拆成了两个阶段。这样设计的好处是盘点操作员只负责录入实盘情况后续的差异原因分析和责任认定由另一拨人去处理职责边界清晰。我在实际项目里看到很多同类系统把这两步揉在一起导致一个普通操作员就能直接改资产状态容易留下数据安全隐患。3.4 报表导出ExcelUtil 里藏着什么工具系统提供了分类统计、部门资产明细、领用归还记录三类报表导出功能用的是一种轻量级方案用 Java 的 POI 库直接操作 XSSFWorkbook 生成 Excel 文件通过HttpServletResponse以流的形式写回浏览器。// ExcelUtil.java 导出核心方法 public static void export(ListString headers, ListListString data, HttpServletResponse response) { XSSFWorkbook workbook new XSSFWorkbook(); Sheet sheet workbook.createSheet(Sheet1); // 1. 写表头 Row headerRow sheet.createRow(0); for (int i 0; i headers.size(); i) { headerRow.createCell(i).setCellValue(headers.get(i)); } // 2. 写数据行 for (int i 0; i data.size(); i) { Row row sheet.createRow(i 1); ListString rowData data.get(i); for (int j 0; j rowData.size(); j) { row.createCell(j).setCellValue(rowData.get(j)); } } // 3. 设置响应头并输出 String fileName URLEncoder.encode(资产报表.xlsx, UTF-8); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filename fileName); workbook.write(response.getOutputStream()); workbook.close(); }这个工具类把导出的核心逻辑封装成了一个静态方法Controller 调用的时候只需要准备表头数组和二维数据列表不用关心 Excel 格式细节。需要注意两点文件名用URLEncoder.encode处理避免中文文件名在部分浏览器里乱码导出 Excel 对历史数据量大的表比较吃内存这是轻量方案的已知局限。如果资产量超过 10 万条导出时建议加个异步任务 下载链接这是后话了。4. 避坑指南跑这个项目最容易翻车的三个地方4.1 环境排查JDK 版本和 Tomcat 版本不匹配导致启动失败现象在本地启动 Tomcat控制台直接报UnsupportedClassVersionError或者启动后页面 404没有报错信息但登录页就是出不来。原因源码是在 JDK 8 环境下写的。如果你的本机装的是 JDK 11 或更高版本编译阶段可能没问题因为 Maven 的编译插件有版本兼容但 Tomcat 版本不够新的话对旧 class 文件的加载处理会出现版本不兼容。另外如果源码包里的web.xml声明的 servlet 版本是 3.1 而你的 Tomcat 是 8.0 以下也会出现类似的启动异常。解决确认 JDK 是 8 并设置好JAVA_HOME环境变量Tomcat 用 8.5.x 版本这两个版本组合是这套代码最成熟的运行环境。启动之前检查pom.xml里maven-compiler-plugin的source和target是否都是 1.8如果不是改成 1.8 让 Maven 重新编译。如果你是 Mac 环境注意别同时存在多个 JDK 版本导致命令行用的是 11 而 IDE 用的是 8这种环境错乱问题不好排查。4.2 数据库初始化的坑SQL 脚本导入报错或中文乱码现象导入init.sql脚本时提示字段长度超限或者导入成功但页面显示的中文都是问号?。原因典型的字符集问题。数据库连接串里没设置characterEncodingUTF-8或者 MySQL 数据库本身的字符集是 latin1导致中文数据存不进去。字段长度超限通常是 SQL 脚本里的 VARCHAR 长度在导入时因为字符集换算被判定超限。解决建库的时候指定字符集执行 SQL 前先跑一句CREATE DATABASEassetDEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;这样后面导入表结构就没问题。还要检查jdbc.properties里的连接串确认一定要带上?useUnicodetruecharacterEncodingUTF-8注意 JMeter 或命令行直接导入脚本时也要用--default-character-setutf8参数。4.3 登录拦截器的白名单遗漏导致没有验证码模块现象输入验证码时发现没有验证码图片或所有页面请求都被拦截器拦住了登录后跳转正常但访问静态资源 404。原因这套系统的登录模块用的是用户名 密码的简单校验没有接验证码接口。拦截器只放行了/login和/logout两个路径静态资源路径比如 css、js、图片放在/static/下没加白名单导致页面加载时样式全丢。解决这个东西不能只靠代码要把拦截器的白名单加完整。在AuthInterceptor的preHandle方法里把/static/**加入放行列表确保 AdminLTE 模板的样式文件能正常加载。如果你确实要加验证码功能可以用一个简单的做法在登录 Controller 里返回一张图片流同时把验证码存到 Session然后在拦截器里比对。我一般会用 Servlet 原生 API 生成这种简单的图形验证码省得再引入一套复杂框架。5. 把项目跑起来从拿到源码到看到登录页的完整操作过程5.1 准备工作需要用到的软件清单按照我实际验证过的环境来配稳的。操作系统 Windows 10/11 或 macOS 都可以需要安装的软件如下JDK 1.8确保java -version输出版本里包含 1.8Maven 3.6 或 3.8用于依赖下载和打包Tomcat 8.5把 WTP/插件里的 Tomcat 关掉避免冲突MySQL 5.7 或 8.0用本机安装即可IDEA 或 Eclipse推荐 IDEA社区版或旗舰版都行5.2 导入数据库和初始化数据先把工程解压到某个目录别放在有中文和空格的路径下防止各种莫名的构建报错。然后找到源码包里的sql文件夹打开 MySQL 命令行执行mysql -u root -p init.sql默认数据库名是asset_db里面预制了几条基础数据系统管理员账号 admin普通操作员账号 user01各有一个默认密码。这部分数据在t_user表的初始化 INSERT 里MD5 加密方式存储。需要确认密码算法是什么你可以去UserServiceImpl里看密码校验那段代码通常会用DigestUtils.md5Hex来做。如果连接串配置的数据库名和你实际建的库名不一致在页面登录时就会持续报数据库连接超时。5.3 修改数据库配置与启动 Tomcat打开jdbc.properties文件按你的本地环境修改下列参数jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/asset_db?useUnicodetruecharacterEncodingUTF-8 jdbc.usernameroot jdbc.password你的数据库密码然后回到 IDEA打开 Maven 面板执行clean package把工程打成 WAR 包。确定没有编译错误之后把target/asset-management.war部署到 Tomcat 的 webapps 目录或者如果你用 IDEA 启动 Tomcat配置好 Application context 为/asset。启动完成后浏览器访问http://localhost:8080/asset/login看到登录页就说明整套环境通了。输入管理员账号进去之后建议按这个顺序做一遍基础操作验证系统是否正常先在「分类管理」里加一个分类再去「资产管理」里新增一条资产看看资产编号是否正确生成然后到「领用管理」里发起一条领用申请审批通过后去台账里确认状态已变为「领用中」最后做一次盘点把账面状态改一下看差异记录是否生成。这套流程走下来你对代码里状态机的理解会比光看代码强得多。6. 二次开发思路把 Excel 台账迁移成一套可用的内部系统6.1 加一个「资产图片」字段的完整流程很多公司有拍照留存资产实物的需求比如贵重设备在入库时拍一张照片存到系统里这个需求在这个系统里改造起来可以直接照着四步走。第一步在t_asset表加字段ALTER TABLE t_asset ADD COLUMN asset_img VARCHAR(255) DEFAULT NULL COMMENT 资产图片URL;。第二步在Asset.java加private String assetImg;属性。第三步在AssetMapper.xml的resultMap和insert/update语句里加入字段映射同时新增ListAssetImg这种嵌套查询也行但单个 URL 字段直接加主表更好。第四步在新增资产的 JSP 页面加input typefileController 里做文件上传把文件保存到指定目录并往库里回填一个相对路径 URL。文件上传这里要注意一个点Tomcat 默认上传大小限制是 2MB如果你需要传大图在spring-mvc.xml里配一个MultipartResolver并把maxUploadSize调大到 10MB 或 20MB。我一般会把上传目录配置到项目外部比如D:/asset-upload/这样重新部署不会丢图片也可以接一个 OSS 云存储来替代本地目录。6.2 给报表加一个「资产月折旧」统计维度如果你的使用场景涉及到财务对账「资产折旧」几乎是必改的功能。固定资产的原值、残值率、折旧年限是一个经典的财务计算三元组代码实现不复杂难点在于把折旧计算做成可配置的。思路是给t_asset加depreciation_year折旧年限和residual_rate残值率默认 5%然后在报表模块新增一个按月份求折旧额的方法。// 新增的计算逻辑 public BigDecimal calculateMonthlyDepreciation(Asset asset) { // 残值 原值 * 残值率 BigDecimal residualValue asset.getPrice().multiply(asset.getResidualRate()); // 月折旧额 (原值 - 残值) / (折旧年限 * 12) BigDecimal depreciableBase asset.getPrice().subtract(residualValue); int totalMonths asset.getDepreciationYear() * 12; return depreciableBase.divide(BigDecimal.valueOf(totalMonths), 2, RoundingMode.HALF_UP); }这个方法的两个隐藏细节值得注意第一BigDecimal的除法一定要指定小数位和舍入方式直接除整数会抛ArithmeticException这是财务计算里极容易出现的问题第二折旧计算的取整方式采用HALF_UP四舍五入但严格来说财务上更常用「按月分摊、按年调整」的规则如果你负责的项目后续要对接财务系统这块策略最好和财务确认过再实现。报表 SQL 里可以用SUM按部门或分类对折旧额做聚合然后复用ExcelUtil导出这样一次改造就能同时满足页面查看和线下归档两个场景。6.3 技术债务整理JSP 升级成前后端分离如果你不只是想内部用而是打算长期在这个系统上继续加功能我的建议是把前端迁到 Vue Element 这类成熟的后台组件库后端在现有 Controller 基础上加一套返回 JSON 的 RESTful 接口。改造顺序不要乱我的习惯是先把核心接口抽出来登录获取 Token、资产列表、新增资产、领用接口每个接口写死返回统一的ResultT结构体前端全部走 HTTP 调用等接口稳定性有保证了再逐步把 JSP 页面替换掉。不要想着一次性把所有页面全切过来资产查询这种高频页面先动盘点这种低频页面可以留着慢慢改。老项目最大的风险其实不在技术本身而在你没有完整跑通业务就动手重构结果改着改着发现某个隐藏的状态逻辑没考虑到。我说的经验是不管多急着改造第一步永远是先把这个系统完完整整用一遍把代码和操作路径对起来你在心里画一张流程图资产状态是怎么从 1 变成 2、再从 2 变成 3 的每一步在代码里的入口和出口在哪里都标注清楚再动刀。从那以后我每次接手这类管理系统的源码都强制自己先跑一遍完整业务流再做任何改造。希望这份拆解笔记能帮你把这套源码跑明白也让你在下一步写自己的代码时少踩几个我踩过的坑。本文还有配套的精品资源点击获取