简介:一套基于Java技术的企业固定资产管理系统完整源代码包,适用于Java Web开发者、学生及需要资产数字化管理的中小企业;系统支持固定资产的增删改查,以及采购、入库、分配、折旧、盘点、报废等生命周期管理,并可通过ODBC灵活适配SQL Server、MySQL、Oracle等数据库,具备较好的跨平台和扩展性。压缩包共606个文件,大小8.73MB,主要包含Java源文件、JSP页面、编译后的class文件、Jar依赖库、XML配置等,结构清晰,便于按模块查阅。其中“必看.pptx”介绍了系统架构、数据库表结构及ODBC连接配置方法,有助于快速上手;目前已有1686人学习浏览。对想要提升Java Web开发、数据库集成及业务逻辑设计能力的读者来说,这是一份具有实践价值的参考资源,可作为企业固定资产管理系统的基础模板进行定制和扩展。
1. 拿到固定资产管理系统源码包后,先别急着看代码
固定资产管理系统Java项目源码这种压缩包,在技术社区里几乎是流通量最大的Java Web资源之一:写毕业设计的学生要找它,公司行政想搭一套内部资产台账也在找它,Java新人想借现成项目学分层结构还是在找它。但我说句实话,这类源码包解压后最容易把人气走的不是业务逻辑,而是系统根本启动不起来——作者通常把自己电脑上的数据库密码、端口号、项目访问路径写死在配置文件里,换个环境就报错。这篇笔记就把拿到rar之后的完整路径讲清楚:先判断技术栈,再改配置把系统跑起来,然后看资产台账、领用、归还、报废这些核心业务代码落在哪一层,接着是一组我踩过的启动和改造高频坑,最后给一个把存量资产批量导入系统的进阶技巧。适合三种人:要交毕设课设想快速跑通并能讲清楚代码的学生;想给部门搭一个够用资产库的非专业开发者;以及想借现成项目学Spring + MyBatis这类分层写法的新人。
2. 把rar变成能跑的系统:技术栈识别与最小启动步骤
2.1 解压后先别急着导IDE:先确认它到底是什么形态的项目
固定资产管理系统这类源码包,解压后最常见的结构是两种:一种是传统Maven Web项目,根目录有pom.xml,代码按src/main/java和src/main/webapp组织,部署到Tomcat跑;另一种是Spring Boot单体项目,同样有pom.xml,但启动方式是一个main方法直接起内嵌容器。还有极少数老项目不带Maven,直接用src加WebContent的普通Web项目结构。
我一般拿到压缩包后会先做三件事:第一步,看根目录有没有pom.xml,有它就是Maven项目,这决定了后续依赖下载和打包方式;第二步,找数据库脚本,一般在根目录、db目录或sql目录下,文件名通常是asset.sql、assets.sql或init.sql;第三步,找配置文件,SSM架构的老项目是jdbc.properties加spring-mybatis.xml,Spring Boot项目则是application.properties或application.yml。这三样确认完,整个项目的运行路径就清晰了,导入IDE时选Maven Project,等依赖下载完再继续。
这里有个容易翻车的细节:不要直接把rar里的文件夹拖进IDE点运行。我先解压到纯英文路径下,路径里不要带中文和空格,然后把项目用Maven方式导入到IDE。有些读者直接把压缩包解压到桌面“新建文件夹”里,结果Tomcat部署时路径解析出问题,报一堆诡异的ClassNotFound,这种折腾完全不值得。
2.2 建库:把SQL脚本跑进去的干净姿势
固定资产管理系统的数据库初始化是第一步,也是大多数人第一次卡住的地方。脚本里的SQL一般是建库建表加初始数据一体的,但库名、字符集、初始账号密码未必是你想要的。我习惯用命令行执行,先确保本地MySQL服务是启动状态,然后执行下面的操作:
mysql -u root -p --default-character-set=utf8mb4 CREATE DATABASE IF NOT EXISTS asset DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE asset; SOURCE /home/you/project/asset.sql;注意这里的SOURCE要写脚本在你机器上的绝对路径,Windows下路径分隔符要写成斜杠,比如SOURCE D:/work/asset.sql。执行过程中如果报错,先不要继续,停下来看是第几句SQL出问题。最常见的是建表语句里用了和当前MySQL版本不兼容的字段类型,或者脚本里带了删除旧表的DROP语句而你的库权限不够。我处理这类问题的方式是:定位到报错的那一段SQL,单独复制出来手动执行,一段一段过,而不是反复跑来跑去找错在哪。
脚本执行完后,顺手验证一下关键表。通常资产主表叫asset或t_asset,用户表叫user或t_user,部门表叫department。用以下SQL看一下主要表是否存在和数据情况:
USE asset; SHOW TABLES; SELECT id, name FROM user LIMIT 5; SELECT id, asset_name, status FROM asset LIMIT 5;确认有数据后再做下一步。很多源码包的表名带统一前缀,比如t_asset、t_category,这说明作者的实体类和Mapper里都带着这个前缀。你后面写任何SQL和改代码时,必须延续前缀风格,否则Java里查的是asset,库里却叫t_asset,连表名都对不上。
2.3 改配置启动:四个必要修改点一次到位
数据库导入成功后,系统跑不起来的原因基本都集中在配置文件。传统SSM项目的jdbc.properties在src/main/resources下,Spring Boot项目则是application.properties。一般要改四个地方:数据库地址、用户名、密码、以及项目访问路径。先看老项目的jdbc配置:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/asset?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=改成你自己数据库的密码 # 连接池参数,视项目而定 jdbc.initialSize=5 jdbc.maxActive=20这四个参数里,driver类名有个大坑:如果本地MySQL是5.x版本,用com.mysql.jdbc.Driver没问题;如果是8.x版本,必须改成com.mysql.cj.jdbc.Driver,否则启动时直接报ClassNotFound。url里的serverTimezone也容易漏,不配置的话会报时区错误。字符集参数characterEncoding=utf8要和建库时的字符集保持一致,都统一用utf8mb4就不会出现中文乱码。
Spring Boot项目的话,配置更集中,通常还带着端口号和上下文路径:
server.port=8080 server.servlet.context-path=/asset spring.datasource.url=jdbc:mysql://localhost:3306/asset?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=你的密码 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver配置里这个context-path特别重要,它决定了访问地址。很多源码包默认写的是/项目名,如果没写就是根路径。我见过太多人启动成功后访问http://localhost:8080/却看到404,最后发现还要加一层/asset前缀。
配置改完后启动项目,SSM项目一般要打包成war丢进Tomcat的webapps目录,Spring Boot项目直接运行main类。用Maven打包传统Web项目时,执行下面的命令:
mvn clean package -DskipTests # 打出的war包在target目录下,复制到Tomcat的webapps目录后启动Tomcat2.4 启动成功后先点这三处,确认不是假跑通
系统能打开登录页不算跑通,我建议按这个顺序做冒烟验证:第一,用初始账号登录。这类源码的初始账号密码一般写在asset.sql里,常见的是admin/admin123或admin/123456,但不要猜,直接查SQL脚本里的INSERT语句最稳妥。第二,打开资产列表页,确认能查出刚才验证过的那几条数据,页面不报500。第三,点一次新增资产或编辑资产,确认表单提交链路是通的。这三步走完,系统才算真正能用。
3. 资产台账/领用/报废是怎么实现的:表设计与三层链路
3.1 资产台账的表设计:一张核心表看穿整个系统
固定资产管理系统无论界面多复杂,最核心的永远是那张资产主表。下面是一个我从多个模拟项目里提炼出来的通用结构,字段命名做了简化,但骨架和真实项目基本一致:
CREATE TABLE asset ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', asset_code VARCHAR(32) NOT NULL UNIQUE COMMENT '资产编号', asset_name VARCHAR(100) NOT NULL COMMENT '资产名称', category VARCHAR(32) COMMENT '资产分类', purchase_date DATE COMMENT '购置日期', original_value DECIMAL(12,2) COMMENT '原值', department VARCHAR(64) COMMENT '使用部门', keeper VARCHAR(32) COMMENT '使用人', status TINYINT COMMENT '状态:1在库 2领用 3维修 4报废', remark VARCHAR(255) COMMENT '备注', update_time DATETIME COMMENT '更新时间', KEY idx_status (status), KEY idx_department (department) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='资产台账表';asset_code这个字段是整个表设计的关键,它必须有唯一约束。后面做Excel导入、资产盘点、扫码查询,全靠这个编号定位数据,一旦出现重复编号,轻则插入失败,重则盘点时账实对不上。status字段我用TINYINT数字存储而不是直接存中文,原因是页面下拉框、业务判断和统计报表都要按状态过滤,数字做条件比字符串高效且不容易出错。金额字段坚持用DECIMAL,不能因为偷懒用FLOAT或DOUBLE,资产原值涉及折旧和统计,浮点误差会造成对账麻烦。
表设计里还要注意部门和人员的设计取舍。小体量的系统直接在主表里存department和keeper两个字符串字段,够用而且查询简单;如果公司组织架构经常调整,则应该拆成部门表和人员表,主表只存部门ID和人员ID。什么时候必须拆?当你需要按部门统计资产总值、人员离职后还要追溯名下历史资产时,就必须拆,否则离职人员记录一改,历史台账全乱了。
3.2 从页面到数据库:一条新增资产的完整链路
理解了这个项目怎么把一条前台数据存进数据库,就等于理解了整套SSM分层的思路。常见的项目里,请求路径是JSP页面 → Controller → Service → Mapper → MySQL。以新增资产为例,Controller接收参数后转给Service,Service先做业务校验再调Mapper插入。我贴一段最典型的新增资产Service方法,代码已经做了精简:
@Override @Transactional public void addAsset(AssetDTO dto) { // 1. 编号唯一性校验,防止并发插入产生重复 if (assetMapper.existsByCode(dto.getAssetCode()) > 0) { throw new BizException("资产编号已存在"); } // 2. DTO转实体,状态默认在库,不从页面接收 Asset asset = new Asset(); asset.setAssetCode(dto.getAssetCode()); asset.setAssetName(dto.getAssetName()); asset.setCategory(dto.getCategory()); asset.setPurchaseDate(dto.getPurchaseDate()); asset.setOriginalValue(dto.getOriginalValue()); asset.setDepartment(dto.getDepartment()); asset.setKeeper(dto.getKeeper()); asset.setStatus(1); asset.setUpdateTime(new Date()); // 3. 插入数据库 assetMapper.insert(asset); }这段代码有三个设计意图值得说。第一,@Transactional注解保证整个方法是一个事务,如果后面insert失败,前面的操作自动回滚,不会出现数据写一半的情况。第二,编号校验放在Service而不是Controller,是因为Service才是业务规则的最终守门员,页面容易被绕过。第三,status默认值在Service里强制设为1,而不是由前端页面传参,防止有人构造请求把资产直接改成报废状态。DTO和Entity分离也是这个项目里常见的设计,页面参数先进DTO,再手动转成实体,避免前端字段直接批量映射到数据库列。
3.3 领用/归还/报废:状态流转别只写增删改查
固定资产系统的业务难点不在增删改查,而在状态流转。资产从入库、领用、归还、维修到报废,每一步都有前置条件,不能随便改。常见做法是写一个专门的状态变更Service方法,每次变更前先查当前状态,校验允许的状态路径再更新:
@Override @Transactional public void scrapAsset(Integer id, String operator) { Asset asset = assetMapper.selectById(id); if (asset == null) { throw new BizException("资产不存在"); } // 已报废或维修中的资产不能再走报废流程 if (asset.getStatus() == 4 || asset.getStatus() == 3) { throw new BizException("当前状态不允许报废"); } // 报废时记录操作日志,后面可以追溯到人 assetMapper.updateStatus(id, 4); assetLogMapper.insertLog(id, "报废", operator); }这里最关键的决定是:报废用软删除而不是物理删除。我见过一些简化版项目,报废直接把记录DELETE掉,这属于翻车设计。资产台账属于追溯类数据,年终审计、折旧计算、资产盘点都需要历史记录,物理删除后所有关联日志和统计全部失去意义。正确做法是状态置为报废,数据永远留在库里,后续统计时用状态字段过滤即可。同理,领用和归还也应该走状态更新加操作日志的路子,这样你随时能回答“这台电脑上个季度在谁手里”这类资产追溯问题。
4. 改造成自己的系统:5个必调参数与三类改法
4.1 5个必调参数:按这张表检查一遍再谈二次开发
把系统跑起来只是第一步,真正要让它贴合自己的使用场景,有五个参数几乎是必改的。下表列出的都是我在实际改造中反复触碰到的地方,配置文件位置因项目架构而异,但改动思路是通用的:
| 参数 | 常见位置 | 不调会怎样 | 推荐设置 |
|---|---|---|---|
| 数据库连接 | jdbc.properties / application.properties | 连不上库,启动即失败 | 指向本地或内网数据库 |
| 服务器端口与上下文路径 | server.port / server.servlet.context-path | 端口冲突或访问404 | 有冲突就换8081等空闲端口 |
| 文件上传保存路径 | 配置文件中的upload.path或web.xml | 上传的图片/附件重启就丢 | 改到磁盘固定目录,并建虚拟路径映射 |
| Excel模板路径 | 代码中模板常量或resources/template目录 | 导出报表时找不到模板 | 把模板放到classpath下,用相对路径读取 |
| 初始管理员密码 | asset.sql里的INSERT语句 | 用默认密码登录存在安全风险 | 登录后立即改密码,并清掉SQL里的明文初始值 |
4.2 加一个“购置年份”筛选:动一处SQL和两行页面
多数固定资产系统自带的列表页只有分页和关键字搜索,但实际使用中大家最常问的问题是“去年买的设备有哪些”。给列表加一个按年份筛选的小功能,是理解这个项目改造流程最典型的练手。核心改动在Mapper的XML里,用MyBatis动态SQL按条件拼接:
<select id="pageAsset" parameterType="map" resultType="com.demo.entity.Asset"> SELECT * FROM asset <where> <if test="assetName != null and assetName != ''"> AND asset_name LIKE CONCAT('%', #{assetName}, '%') </if> <if test="year != null and year != ''"> AND YEAR(purchase_date) = #{year} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY id DESC LIMIT #{offset}, #{pageSize} </select>这段XML里有三个细节值得留意。一是 标签会自动去掉多余的AND,避免手动拼接SQL时第一个条件前多出AND导致语法错误。二是YEAR(purchase_date)这个写法直观,但它会让purchase_date列上的索引失效,全表扫一遍。资产表在几十万行以内完全没有压力,超过百万行后就要改成范围查询,用purchase_date >= '2024-01-01' AND purchase_date < '2025-01-01'代替YEAR函数。三是LIMIT的offset和pageSize用map传参,这是MyBatis分页最常见的传法。页面端只需要在下拉框里放几个年份选项,选中后带上year参数重新查询即可。
4.3 自定义资产编号前缀:把编号生成规则抽出来
资产编号是系统里最容易被写崩的地方。很多原版代码在Controller里直接写死了编号生成逻辑,比如用System.currentTimeMillis()截几位拼上去,或者用固定前缀加随机数。这样做的后果是:编号没有可读性,部门前缀想换的时候要把所有代码里的字符串拼接都找出来改一遍。我建议把编号生成统一收敛到一个工具类:
public class AssetCodeGenerator { private static final String DEFAULT_PREFIX = "ZC"; public static String generate(String prefix, long sequence) { if (prefix == null || prefix.trim().isEmpty()) { prefix = DEFAULT_PREFIX; } String datePart = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); String seqPart = String.format("%04d", sequence); return prefix + "-" + datePart + "-" + seqPart; } }调用时只要传入部门前缀和当天的序号,比如财务部传CW,生成的编号就是CW-20250612-0001。把前缀从调用方传入,而不是在工具类里写死,这样以后要改编号规则只需要动这一个类。sequence从哪里来?常见做法是查当天该前缀下已有的最大序号加1,或者在库里加一张编号序列表专门做计数器。前一种实现简单但并发时会撞号,后一种更稳但要维护额外一张表。我的建议是小项目先用查询最大值加1的方式,等真的出现并发问题了再上序列表。
5. 常见问题排查:启动和运行期的5个高频坑
5.1 Tomcat端口被占用,改了端口还是启动报错
现象:启动Tomcat时立刻报Address already in use,换了个端口再启动,依然报同样的错,甚至报多个Connector错误。
原因:换端口时只改了HTTP连接器的8080端口,但项目里还配置了AJP连接器或JMX远程端口,它们还占着旧端口。另一个隐蔽原因是webapps下已经存在同名解压目录,二次部署时资源没释放干净。
解决:改端口要整体改,在Tomcat的server.xml里同时修改Connector端口,并确认没有关闭其他占用该端口的进程。Windows下用netstat -ano | findstr 8080查占用进程,Linux下用lsof -i:8080。另外,重新部署前清空Tomcat的work目录和webapps下的旧项目目录,避免新旧文件混在一起编译出问题。
5.2 页面打开后全是中文乱码,查了JSP还是乱
现象:登录页和列表页的标题、按钮都是问号或乱码,但数据库里数据是正常的。
原因:这个坑通常不是单点问题,而是三层字符集不一致。数据库连接url里没加characterEncoding=utf8,或者JSP页面没声明UTF-8编码,再或者MySQL表本身建的是latin1字符集。
解决:按三层逐一排查。第一层,确认建表语句用的是utf8mb4,已存在的表用ALTER TABLE asset CONVERT TO CHARACTER SET utf8mb4;补救。第二层,jdbc连接url里带上useUnicode=true&characterEncoding=utf8。第三层,所有JSP页面顶部声明pageEncoding="UTF-8"。这三处统一后,乱码基本绝迹。不要在代码里逐条改字符串,那是治标不治本。
5.3 资产列表打开就报500,错误信息显示表不存在
现象:系统能登录,但点开资产列表时页面报错,Tomcat日志里出现Table 'asset.asset' doesn't exist。
原因:库里真实的表带前缀叫t_asset,但MyBatis的Mapper映射和实体注解里写的是asset,或者反过来。这类源码的作者经常在表名前缀上不统一,SQL脚本里建的是带前缀的表,Java代码里却把前缀丢了。
解决:统一表名。优先改Java代码侧,因为库里数据你已经导入了,没必要动库。打开对应的Mapper接口或XML文件,把@@TableName("asset")或SQL语句里的表名改成和库一致。改完后重启前,用SQL先验证一下对应关系,SELECT * FROM t_asset LIMIT 1能通,再启动应用。这个坑属于一眼就能看穿但很多人被带偏的典型问题,我最初排查时甚至去改了建库脚本,结果把库重建了一遍才发现只是Java侧少了个前缀。
5.4 图片上传成功但重启后全部丢失
现象:上传资产照片后当时能正常显示,Tomcat一重启或IDE一重编译,图片全部404,文件也找不到。
原因:上传代码写的是相对路径,图片被保存到了IDE为Tomcat生成的临时部署目录下。这个目录在每次编译重启时会被清空,属于典型的黑匣子问题,表面看路径没错,其实文件压根不持久。
解决:给文件上传配置一个磁盘绝对路径,比如/var/asset_upload/或D:/asset_upload/,然后在Tomcat或Spring Boot里做虚拟目录映射,把用户的访问URL映射到这个磁盘目录。Spring Boot下用addResourceHandlers方法:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:/var/asset_upload/"); }这样配置后,上传文件保存到磁盘固定目录,重启不丢,访问路径固定为/upload/xxx.jpg。注意Windows路径要写成file:D:/asset_upload/,盘符结尾的斜杠方向不要写反。
5.5 数据初始化后初始账号登录不上
现象:SQL脚本执行成功,初始用户也在表里,但用脚本里看到的密码登录时提示用户名密码错误。
原因:这类系统的登录模块普遍做了密码加密,SQL脚本里INSERT进去的password字段值是明文,但登录校验用的是MD5或BCrypt加密后再比对。明文和密文永远对不上。
解决:不要试图在数据库里手动改密码。先看登录Service的校验代码,确认用的加密算法,然后打开项目里的密码加密工具类,生成一个加密后的密码,再UPDATE到数据库:
UPDATE user SET password = '生成的加密值' WHERE name = 'admin';注意加密值要带盐的话,一般直接存在password字段里或单独salt字段,必须先看代码确认格式,否则更新完还是对不上。
6. 进阶技巧:把Excel批量导入接到系统里,省掉一半录入手工活
固定资产系统上线时最痛苦的不是开发,而是把几百上千条老台账录进系统。逐条手录费时费力还容易错,我一般会给这类系统补一个Excel批量导入的入口。实现思路是读取固定模板,每行转成一条Asset记录,复用前面新增资产的Service方法,逐行校验并汇总错误清单。用POI读取Excel的简化代码如下:
public Map<String, Object> importAssets(MultipartFile file) { List<String> errors = new ArrayList<>(); int successCount = 0; try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) { Sheet sheet = workbook.getSheetAt(0); // 第0行是表头,从第1行开始读 for (int i = 1; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (row == null) continue; // 跳过整行为空的数据,这是最常见的脏数据来源 if (isRowEmpty(row)) continue; AssetDTO dto = new AssetDTO(); dto.setAssetCode(getCellString(row.getCell(0))); dto.setAssetName(getCellString(row.getCell(1))); dto.setCategory(getCellString(row.getCell(2))); dto.setPurchaseDate(getDateCellString(row.getCell(3))); try { assetService.addAsset(dto); successCount++; } catch (Exception e) { errors.add("第" + (i + 1) + "行导入失败: " + e.getMessage()); } } } catch (Exception e) { throw new BizException("Excel解析失败"); } return Map.of("success", successCount, "errors", errors); }这段代码里有三个地方是易错的。第一,getCellString方法要判断单元格类型,日期单元格要按日期格式读,公式单元格要getCellFormula,统一转成字符串时用DataFormatter最稳妥,直接调用toString会在数字和日期上翻车。第二,校验和插入放在同一个事务里还是拆开逐行提交,要结合数据量决策;我倾向于逐行独立提交,这样几十条里只有一条错时,其他数据仍然导入成功,错误行返回给用户修改后再传。第三,asset_code在Excel文件里出现的重复值、空值、超长值,全部要预先校验,否则数据库唯一约束会把你拦在半路。
我第一次给模拟项目X做批量导入时,漏掉了空行判断,结果用户把模板里没删干净的空行也提交了,一次性插进去百十条空白资产,清理时只能逐条重设状态。后来就养成了习惯:导入前先统计Excel总行数告诉用户确认,导入中每行异常都捕获并汇总,导入后返回错误清单,三件套路缺一不可。这套思路对固定资产系统来说很实用,希望帮到你。
本文还有配套的精品资源,点击获取