简介:本资源是一套完整的基于SpringBoot的固定资产管理系统毕业设计资料,面向计算机专业本科生及Java初学者,解决企事业单位资产登记、分类管理、审批流程与用户权限控制等核心管理需求。压缩包共245个文件,包含44个Java源码文件(涵盖UserController、AssetController、UserRealm等关键类)、30个HTML页面模板、20个JS交互脚本、13个XML配置文件、12个CSS样式文件及1个建库SQL脚本,完整支撑系统前后端开发与部署;包体大小为36.6MB。已有513人学习下载,资源内容与论文高度对应,覆盖从需求分析、数据库设计(含资产表、审批表、用户表等)、六大功能模块实现(登录、资产查询、分类管理、审批待办、个人资产、用户管理)到系统测试的全流程,附带可直接运行的项目源码与结构清晰的毕业论文文档,便于快速理解MVC分层架构与企业级资产管理业务逻辑。 咱们这次聊的题目是“基于SpringBoot固定资产管理系统的设计与实现(论文+项目源码)”,一看就是典型的计算机类毕业设计选题。这个题在每年的毕设题目里出现频率极高,不是没道理的:既有业务逻辑可讲,又能把SpringBoot、MyBatis、数据库设计、权限控制这些核心知识点全串起来,还不用涉及太复杂的分布式场景,很适合作为本科阶段的综合练手项目。
从本质上说,固定资产管理系统解决的是企业资产“从哪来、在谁手里、状态如何、用到什么时候”的问题。现实中大量中小企业还在用Excel台账管资产,资产编号靠手写、领用人靠口头通知、报废审批靠线下签字,盘点时几个人对着表格挨个核对,效率和准确率都很感人。做成系统之后,资产的全生命周期——从入库、领用、归还、调拨、维修到报废——都能在线上留痕,审批有记录、数据可统计、责任能追溯,这就是这个系统真正的价值所在。
这套系统适合谁来参考?第一类是正在准备毕业设计的在校生,拿来捋清楚一个完整Web项目的结构;第二类是刚入门SpringBoot、想找个“不太难又完整”的实战项目练手的朋友;第三类是想给公司内部做个简单资产管理工具的后端开发。前两类读者占绝大多数,所以下面的内容我尽量兼顾“论文怎么写”和“代码怎么落地”两条线,既有逻辑拆解,也有能直接抄的实操细节。
1. 需求分析与整体设计思路拆解
1.1 固定资产管理的真实痛点与需求边界
为什么不建议直接“拍脑袋”写功能,而是要先梳理痛点?因为很多毕设项目的通病是功能堆砌——用户管理、部门管理、资产管理、报表管理全都有,但每个模块都只是增删改查,看不出设计思路。用真实场景驱动需求分析,论文里才有东西写,答辩时也才经得起追问。
我接触过的小企业资产管理员,日常工作大概是这样的:每个月要更新一次Excel台账,新买的电脑要手动编一个资产编号填进去;有人离职时要查他名下有没有没归还的设备;每隔半年要组织一次全公司盘点,打印一大摞表格挨个房间核对。这些场景对应到系统里,就是几个核心诉求:
- 资产信息要有统一的、不可重复的编码规则,能快速查到某件资产的当前位置和责任人
- 领用、归还、维修、报废这些操作必须走线上流程,而且每一步都要留下操作记录
- 管理层能随时看到资产分布情况,比如哪个部门的固定资产最多、哪些设备即将到达报废年限
- 权限要清晰:普通员工只能查看和申请,部门主管能审批,资产管理员能做入库、处置,系统管理员管用户和基础数据
把这些痛点翻译成需求后,功能边界就出来了。我建议不要做“大而全”的ERP式系统,那是给自己挖坑。底线是四张核心主表加两张记录表:资产分类表、资产信息表、部门表、用户表,再加上领用归还记录表、维修记录表。围绕这些表展开的操作就是系统的全部核心功能。报表统计和资产预警可以做成加分项,但不建议一开始就铺开。
1.2 功能模块划分与优先级控制
功能模块我建议按下表来划分,同时标清楚优先级,设计论文时也可以照着这个层次来写:
| 功能模块 | 核心操作 | 优先级 | 说明 |
|---|---|---|---|
| 登录与权限 | 登录、退出、密码修改 | 必做 | 基于RBAC模型,角色分管理员、资产管理员、普通用户 |
| 资产分类管理 | 分类的增删改查 | 必做 | 树形结构,支持父分类,比如办公设备、IT设备、家具 |
| 资产信息管理 | 资产录入、编辑、查询、删除、批量导入 | 必做 | 资产卡片为核心,包含状态字段 |
| 资产领用与归还 | 申请、审批、领用登记、归还登记 | 必做 | 考虑轻量审批流程而非全套流程引擎 |
| 资产维修管理 | 维修申请、维修记录、维修完成 | 推荐 | 体现资产全生命周期 |
| 资产报废管理 | 报废申请、审批、处置 | 推荐 | 状态流转,联动统计报表 |
| 报表统计 | 按分类/部门/状态统计,到期预警 | 加分项 | 使用图表展示,论文里可以写“可视化” |
| 消息提醒 | 待审批通知、到期提醒 | 加分项 | 站内提醒即可,不要碰短信、邮件 |
这样划分有一个好处:前三项保证系统“能用”,中间三项保证“业务能闭环”,最后两项是“亮点”。毕设答辩时评审最看重的是“闭环”——也就是一个业务的完整流程有没有走通。比如“领用申请→审批→出库→归还→归还确认”这条链路,就是整个系统里最重要的业务闭环,论文里最好单独拿出来讲。
1.3 为什么选SpringBoot单体架构而不是微服务
这个选择题几乎每次都会有同学困惑。我的建议很直接:毕业设计和个人练手项目,就选SpringBoot单体架构,理由有三条。
第一,规模匹配。固定资产管理系统面向的是几百人到几千人的中小企业,并发量很小,单体架构完全扛得住,硬上微服务反而徒增部署和运维成本。第二,知识点匹配。本科阶段要求掌握的是Spring的IOC/AOP、MyBatis操作数据库、SpringMVC的请求处理链路、基本的安全控制,这些在单体项目里全都能覆盖到。第三,论文好写。微服务要写服务拆分原则、注册中心、网关、熔断降级,每一块都需要大篇幅,如果实际代码没体现出来,答辩时一问就露馅。
单体架构的优势在于:一个应用包含所有模块,部署时打一个Jar包就行,开发调试时在IDE里一键启动,没有跨服务调用的复杂性。这并不代表项目的代码可以随意堆在一个类里,单体架构同样要求清晰的分层——Controller只做参数接收和响应封装,Service层写业务逻辑,Mapper层只做数据访问,层与层之间用接口隔离。这一点在第二章会详细说。
2. 技术选型与核心配置说明
2.1 技术栈清单及各组件选型理由
技术选型这部分在论文里一般是“系统开发环境”章节,别小看这一段,评委经常会问“为什么用这个不用那个”。把选型理由想透,比堆一堆名词有用得多。
后端 :SpringBoot。建议用2.7.x版本,不是越新越好。3.x版本开始强制要求JDK17,而很多学校的实验环境和教程还是基于JDK8,遇到版本问题会卡住进度。SpringBoot 2.7.x是兼容JDK8的最后一个稳定大版本,生态成熟,网上资料也最多,最适合这个题目。
持久层框架 :MyBatis-Plus。这个东西属于“用了就回不去”的类型,内置的单表CRUD不用写XML,分页插件一行配置就搞定,代码生成器还能直接生成实体类和Mapper接口。相比原生MyBatis少写大量重复代码,相比Spring Data JPA又更容易理解SQL是怎么执行的。论文里可以提一句“基于MyBatis增强工具,保留SQL灵活性的同时简化单表操作”,这个说法十分稳妥。
数据库 :MySQL 5.7或8.0都可以。8.0的窗口函数更强,但对这个项目没什么影响,如果你本机装的是5.7就继续用5.7,没必要重新折腾。
前端 :这是最容易纠结的地方。两个选择:一是用Thymeleaf模板引擎做服务端渲染,前后端代码在同一个工程里;二是用Vue单独做前端,通过接口与后端交互。我的建议:如果你想省时间、把精力放在后端业务上,选Thymeleaf;如果论文想写“前后端分离架构”,选Vue。但是要注意,前后端分离意味着要处理跨域、Token校验、接口联调这些额外问题,工作量至少多出30%。这个选择题没有标准答案,关键是你在论文里的技术描述要和代码实际实现一致,这是最基本的底线。
权限认证 :JWT(JSON Web Token)或者Session都可以。JWT在现在的项目中更主流,前后端分离场景下天然适配,不需要在服务端维护会话状态。具体实现思路后面第四章会详细讲。
其他组件 :文件上传用本地存储即可,不推荐引入FastDFS或MinIO,徒增部署复杂度;Excel导入导出用EasyExcel或Hutool的工具类,代码量极少;报表图表用ECharts,通过接口返回JSON数据,前端渲染成饼图、柱状图。
2.2 后端项目分层与包结构规划
这一节的内容在毕设论文里对应“系统设计”章节,代码里对应工程的包结构。实际开发时把包结构搭好,后面写代码会很顺手,不用到处乱找。
我推荐的分层方式是经典的四层结构:
com.example.fixedasset
├── controller // 接口层,接收参数、返回结果
├── service // 业务层,核心逻辑,接口+实现类
│ └── impl
├── mapper // 数据访问层,继承BaseMapper
├── entity // 实体类,对应数据库表
├── dto // 数据传输对象,定义接收参数和返回参数
├── vo // 视图对象,定义页面展示结构
├── config // 配置类,如MyBatis-Plus分页、Cors跨域
├── common // 公共类,如统一返回结果、异常处理、常量
├── util // 工具类,如JWT工具、Excel工具
└── interceptor // 拦截器,如登录校验、权限校验
每一层都只做自己的事,不许越界。我见过不少同学在Controller里直接写SQL或用 new HashMap 拼参数,这样写快了几天,后面维护和写论文都会很难受。如果你想让代码更规范一些,可以在Service里把“查询列表”“保存资产”“审批操作”这些动作在接口文档里描述清楚,这对接下来的接口测试和论文测试章节都是素材。
2.3 SpringBoot核心配置要点(含application.yml示例)
配置这块是SpringBoot的“甜点”所在,但也是很多新手第一次踩坑的地方。我给你一份可以直接改着用的核心配置:
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/fixed_asset_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
hikari:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 30000
thymeleaf:
cache: false
servlet:
multipart:
max-file-size: 20MB
max-request-size: 50MB
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.fixedasset.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
jwt:
secret: your-secret-key-please-change-in-production
expire: 604800
几个容易忽略的细节:
时间时区 。 serverTimezone=Asia/Shanghai 必须加,否则数据库连接时会报时区错误;如果数据库存的时间比实际时间差8小时,十有八九是这个参数的问题。
逻辑删除 。 logic-delete-field: deleted 表示所有表有一个 deleted 字段用于标记删除,这样执行删除操作时其实是UPDATE,数据不会真正消失,以后想查历史记录还有机会。这个设计在论文“数据库设计”里可以写一条,属于很小的加分项。
驼峰映射 。 map-underscore-to-camel-case: true 让数据库的 asset_code 自动映射到Java类的 assetCode ,省一大串XML别名。
3. 数据库设计与核心业务逻辑
3.1 核心数据表设计与字段规划
数据库设计是固定资产管理系统的“地基”,也是论文里占篇幅最多、最容易被评委追问的部分。表设计得好,后端的CRUD就是流水账;表设计得烂,写业务的时候到处要拼接条件。
我先给你核心的几张表,字段以实用为准,然后逐个说明关键设计意图。
资产分类表 asset_category
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| parent_id | bigint | 父分类,顶级为0 |
| category_name | varchar(64) | 分类名称 |
| category_code | varchar(32) | 分类编码 |
| sort_order | int | 排序 |
| deleted | tinyint | 逻辑删除标记 |
资产信息表 asset
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| asset_code | varchar(64) | 资产编号,唯一 |
| asset_name | varchar(128) | 资产名称 |
| category_id | bigint | 分类ID |
| spec_model | varchar(128) | 规格型号 |
| purchase_date | date | 购置日期 |
| original_value | decimal(12,2) | 原值 |
| net_value | decimal(12,2) | 净值 |
| use_status | tinyint | 在库/领用/维修/报废/待处置 |
| current_user_id | bigint | 当前使用人 |
| current_dept_id | bigint | 当前使用部门 |
| location | varchar(128) | 存放地点 |
| supplier | varchar(128) | 供应商 |
| warranty_end | date | 质保截止日期 |
| buy_user_id | bigint | 经办人 |
| remark | varchar(512) | 备注 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
| deleted | tinyint | 逻辑删除标记 |
领用归还记录表 asset_use_record
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| asset_id | bigint | 资产ID |
| asset_code | varchar(64) | 资产编号(冗余) |
| use_user_id | bigint | 领用人 |
| use_dept_id | bigint | 领用部门 |
| apply_time | datetime | 申请时间 |
| approve_user_id | bigint | 审批人 |
| approve_time | datetime | 审批时间 |
| approve_status | tinyint | 待审批/通过/驳回 |
| approve_comment | varchar(255) | 审批意见 |
| use_start_time | datetime | 实际领用时间 |
| use_end_time | datetime | 预期归还时间 |
| actual_return_time | datetime | 实际归还时间 |
| return_status | tinyint | 使用中/已归还 |
维修记录表 asset_repair_record
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| asset_id | bigint | 资产ID |
| repair_reason | varchar(512) | 故障描述 |
| repair_status | tinyint | 待维修/维修中/已修复/无法修复 |
| repair_company | varchar(128) | 维修单位 |
| repair_cost | decimal(12,2) | 维修费用 |
| repair_time | datetime | 维修时间 |
| finish_time | datetime | 完成时间 |
| operator_id | bigint | 报修人 |
表设计时有几点需要特别注意:
不要存冗余的字符串状态 。比如 use_status 不要用“在库”“领用”这样的中文,要用数字枚举,在Java代码里定义常量或枚举类,页面显示时再转成中文。这个习惯无论对代码可维护性还是论文的“数据字典”设计都有好处。
记录表要冗余资产编号和名称 。资产领用记录表里除了外键,还冗余一个 asset_code 和 asset_name ,这样即使以后资产被删了,历史记录也能看出来当时领的是什么。这是很典型的数仓设计思维,在业务系统里同样适用。
日期字段的边界 。 use_start_time 、 actual_return_time 这种字段不要用 date 类型(只有年月日),要用 datetime ,因为领用和归还可能发生在同一天的不同时刻。只有 purchase_date 这种概念上只关心年月日的字段才用 date 。
3.2 资产编码规则与状态机设计
资产编码是整个系统的基石,它相当于每件资产的“身份证号”。编码规则设计得好,后续查询、盘点、流程关联都会很方便。
我推荐一套简单实用的规则:分类编码 + 购置年月 + 四位流水号。比如一台台式电脑分类编码是 BG (办公设备),2024年6月购入,当月第3台,那资产编号就是 BG2024060003 。用Java实现时,可以利用分类表的 category_code 字段,加上当前年月,再通过SQL统计同类资产当月的数量加一:
String categoryCode = category.getCategoryCode();
String yearMonth = new SimpleDateFormat("yyyyMM").format(new Date());
String prefix = categoryCode + yearMonth;
QueryWrapper<Asset> wrapper = new QueryWrapper<>();
wrapper.likeRight("asset_code", prefix);
Long count = assetMapper.selectCount(wrapper);
String assetCode = prefix + String.format("%04d", count + 1);
这个方案有两点好处:一是资产编号本身就能看出分类和购入时间,不用查数据库;二是不用单独维护一张流水号表,直接在现有表里统计即可,对数据量万级以内的系统完全够用。缺点是并发下可能会重复,但对这个场景来说,加个唯一索引,偶发冲突时让用户重试一次就可以,没必要引入分布式ID。
状态机设计是资产系统里最重要的业务逻辑。资产的状态流转一定要集中管理,不要在多个Service方法里各写各的if-else,否则很容易出现资产状态和记录不一致的情况。建议单独写一个 AssetStatusHandler ,所有状态变更的校验逻辑都往这里收敛:
public enum AssetStatusEnum {
IN_STOCK(0, "在库"),
USED(1, "使用中"),
REPAIRING(2, "维修中"),
SCRAPPED(3, "已报废"),
DISPOSED(4, "已处置");
private final Integer code;
private final String desc;
}
状态流转的核心规则只有几条:在库资产才能被领用;使用中才能申请维修;维修中不能重复申请;报废必须从在库或使用中进入;已报废不能再领用。把这些规则写在状态机的校验方法里,所有服务调用之前先做校验,就能避免脏数据。
3.3 折旧计算逻辑与SQL实现技巧
固定资产系统最容易被问到的业务点就是折旧——“你的系统怎么算折旧的?”如果用最简单的直线折旧法,逻辑并不复杂,但你得把它讲清楚,而且要在代码里实现出来。
直线法公式:月折旧额 =(资产原值 - 残值率×原值)÷ 预计使用月份数。残值率一般取5%,预计使用年限按资产类别区分:电子设备3年、家具5年、建筑物20年。为了简化,可以在资产分类表里加两个字段: depreciation_years 和 residual_rate ,这样不同分类可以有不同参数。
系统的实现思路是:在资产卡片上记录原值、净值、使用起始月份,每月初用定时任务或查询时即时计算当前净值。考虑到毕设场景不建议引入复杂的定时任务框架,用查询时实时计算的方式更简单:
SELECT
a.id,
a.asset_code,
a.asset_name,
a.original_value,
a.purchase_date,
c.depreciation_years,
c.residual_rate,
ROUND(a.original_value * (1 - c.residual_rate) / (c.depreciation_years * 12), 2) AS month_depreciation,
ROUND(
a.original_value -
a.original_value * (1 - c.residual_rate) / (c.depreciation_years * 12) *
TIMESTAMPDIFF(MONTH, a.purchase_date, CURDATE()),
2
) AS net_value
FROM asset a
JOIN asset_category c ON a.category_id = c.id
WHERE a.use_status != 3
这段SQL直接把每件资产的月折旧额、当前净值都算出来,不用在Java代码里做循环,性能也好。需要注意的点是 TIMESTAMPDIFF 是按整月计算的,对月中购置的资产会少算当月折旧,论文里可以写一句“按整月计提折旧,不足整月的不计提”,把规则定清楚就行。
3.4 审批流程的轻量级实现思路
很多同学一提到审批流程就想引入Flowable或Activiti工作流引擎,这东西确实强大,但对固定资产管理系统来说属于“杀鸡用牛刀”。审批引擎的学习成本高,表结构复杂,配置起来也麻烦,单纯为了论文多写两章去引入它,性价比太低。
在这个系统里,审批本质上就是一条状态链。以领用申请为例:提交申请 -> 部门主管审批 -> 资产管理员确认出库 -> 员工签收。这个链路用一张记录表加一个状态字段就能实现,完全不需要工作流引擎。核心表结构就是上面看到的那样,每次审批操作就是更新 approve_status 字段并记录审批意见。
我建议把审批操作封装成一个通用方法,避免每个模块重复写:
public void approve(UseRecord record, Integer approveStatus, String comment, Long approverId) {
// 校验记录状态必须是"待审批",否则抛异常
if (!record.getApproveStatus().equals(ApproveStatusEnum.PENDING.getCode())) {
throw new BizException("当前记录不是待审批状态,无法审批");
}
record.setApproveStatus(approveStatus);
record.setApproveComment(comment);
record.setApproveUserId(approverId);
record.setApproveTime(LocalDateTime.now());
useRecordMapper.updateById(record);
// 审批通过后,联动资产状态变更
if (approveStatus.equals(ApproveStatusEnum.APPROVED.getCode())) {
assetService.changeStatus(record.getAssetId(), AssetStatusEnum.USED);
}
}
用状态字段实现审批有几个好处:表结构简单,代码逻辑一目了然,排查问题时看记录表就能完整还原流程。缺点是无法支持复杂的会签、或签、条件分支,但对这个题目来说完全够用,评委也不会因为你没用Flowable而扣分,反而觉得你设计合理、复杂度控制得当。
4. 核心功能实操与关键代码走读
4.1 用户登录、密码加密与JWT鉴权
登录鉴权是几乎所有系统的入口,面试或答辩时也经常被单独拎出来问。固定资产管理系统的权限模型用最简单的RBAC(基于角色的访问控制):用户表、角色表、用户角色关联表。角色就三种——系统管理员、资产管理员、普通用户,各自对应的菜单和接口权限不同。
密码存储必须加密,严禁明文。推荐用BCrypt算法,Spring Security里自带的 BCryptPasswordEncoder 可以直接用,没有引入Spring Security的项目也可以用 hutool-crypto 里的BCrypt实现。BCrypt的特点是每次加密结果都不同,但校验时能匹配,安全性远高于MD5加盐。
JWT的实现思路是:登录成功后,服务端生成一个包含用户ID、用户名、角色信息的Token返回给前端;前端每次请求在Header里带上 Authorization: Bearer <token> ;后端通过拦截器校验Token是否有效、是否过期,解析出用户信息后放入ThreadLocal,方便后续业务方法获取当前用户。
核心代码大致是这样:
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expire}")
private Long expire;
public String generateToken(Long userId, String username, String role) {
return Jwts.builder()
.setSubject(username)
.claim("userId", userId)
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expire * 1000))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
}
拦截器的注册别忘了放行登录接口和静态资源:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/api/auth/login", "/css/**", "/js/**", "/images/**");
}
}
一个容易被忽视的坑是:JWT是无状态的,服务端无法主动让某个Token失效。在处理“用户修改密码后强制下线”这类需求时会有局限,但毕设场景下不需要考虑,论文里可以提一句“JWT适用于对过期时间要求不敏感的场景”,稳住就行。
4.2 资产CRUD与多条件组合查询
资产列表是系统的核心页面,查询条件通常包括:资产名称(模糊)、资产编号(模糊)、分类(下拉选择)、使用状态(多选或下拉)、购入日期区间。用MyBatis-Plus的LambdaQueryWrapper就能优雅地完成。
public PageResult<AssetVO> pageAsset(AssetQueryDTO dto) {
Page<Asset> page = new Page<>(dto.getPageNum(), dto.getPageSize());
LambdaQueryWrapper<Asset> wrapper = Wrappers.lambdaQuery();
wrapper.like(StringUtils.hasText(dto.getAssetName()), Asset::getAssetName, dto.getAssetName())
.like(StringUtils.hasText(dto.getAssetCode()), Asset::getAssetCode, dto.getAssetCode())
.eq(dto.getCategoryId() != null, Asset::getCategoryId, dto.getCategoryId())
.eq(dto.getUseStatus() != null, Asset::getUseStatus, dto.getUseStatus())
.between(dto.getStartDate() != null && dto.getEndDate() != null,
Asset::getPurchaseDate, dto.getStartDate(), dto.getEndDate())
.orderByDesc(Asset::getCreateTime);
Page<Asset> result = assetMapper.selectPage(page, wrapper);
// 将实体转为VO,补充分类名称、使用人姓名等冗余信息
return convertToPageResult(result);
}
这个查询方式最大的好处是条件可空判断全部内聚在Wrapper里,代码干净,而且参数多了也不容易拼错。有一点要注意:模糊查询最好只在资产名称、编号这种短字段上用 like ,不要在备注这种长字段上做模糊搜索,否则全表扫描会影响性能,论文的性能测试部分如果做了大数据量测试会很尴尬。
批量删除的时候要用事务。JDK11以下用 @Transactional 注解就行,但要注意事务回滚的边界——这个注解加在方法上,只能对运行时异常生效。如果代码里手动捕获了异常,记得 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() 强制回滚,否则删除一半的脏数据就产生了。
4.3 报表统计与图表接口设计
报表模块是答辩时的“讲故事”环节,视觉效果好,也容易讲清楚数据分析思路。ECharts的图无非是饼图、柱状图、折线图,关键是把数据接口设计好。
我建议提供三个核心统计接口:
- 按部门统计资产数量与资产原值(柱状图)
- 按资产分类统计资产占比(饼图)
- 按购置年份统计资产增长趋势(折线图)
接口返回格式一般是:
{
"categories": ["研发部", "财务部", "行政部"],
"values": [65, 42, 28]
}
写SQL时可以用分组聚合一次查出来,避免在Java里循环再查数据库:
SELECT
d.dept_name AS name,
COUNT(a.id) AS value,
SUM(a.original_value) AS total_value
FROM asset a
LEFT JOIN sys_dept d ON a.current_dept_id = d.id
WHERE a.deleted = 0 AND a.use_status != 3
GROUP BY d.dept_name
ORDER BY value DESC
除了统计图,资产到期预警也很有用:把质保即将到期、距报废年限不足一年的资产列出来,让管理员提前关注。用一条SQL加简单的Java时间判断就能实现:
SELECT * FROM asset
WHERE deleted = 0
AND warranty_end IS NOT NULL
AND warranty_end BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 90 DAY)
这条SQL的含义是“质保期在未来90天内到期”。界面显示时可以用不同颜色标记“30天内到期”和“90天内到期”,配合ECharts的警戒线,效果会很好。
4.4 Excel批量导入与导出落地
Excel导入导出在固定资产系统里几乎是刚需,因为企业现有的资产数据基本都是Excel格式的。EasyExcel是阿里的开源库,内存占用比POI低很多,代码也更简洁。
导入的核心逻辑是:前端上传Excel文件 -> 后端接收并解析 -> 校验每一行数据 -> 校验通过的写入数据库 -> 校验失败的错误信息返回给前端。
public Map<String, Object> importAsset(MultipartFile file) {
List<AssetImportDTO> list = EasyExcel.read(file.getInputStream())
.head(AssetImportDTO.class)
.sheet()
.doReadSync();
List<String> errors = new ArrayList<>();
int successCount = 0;
for (int i = 0; i < list.size(); i++) {
AssetImportDTO dto = list.get(i);
try {
validate(dto); // 检查必填字段、分类是否存在、资产编号是否重复
assetService.save(convertToEntity(dto));
successCount++;
} catch (Exception e) {
errors.add("第" + (i + 2) + "行:" + e.getMessage());
}
}
return Map.of("successCount", successCount, "errors", errors);
}
注意这里的行号是 i + 2 ,因为表头占第一行,用户实际看Excel时看到的就是第几行,报错信息这样写才能对得上。这个细节虽然小,但在答辩演示导入功能时很加分,说明你真的在真实场景里用过。
导出的思路反过来,用EasyExcel把查询结果直接写到HttpServletResponse的输出流中,设置好文件名和Content-Type就能触发浏览器下载。为了避免一次性导出全表数据导致内存溢出,导出时也走分页查询,每查出1000条就写一批,写完用 finish() 收尾。
5. 常见问题与排查技巧实录
5.1 项目启动阶段的高频报错与解决方案
我把实际开发中遇到最多的启动报错整理成一张速查表,每一条都是真实踩过的坑:
| 报错信息 | 原因 | 解决方案 |
|---|---|---|
| Failed to configure a DataSource | 没有配置数据源或数据库没启动 | 检查application.yml的url、账号、密码,先确保能连上数据库 |
| Access denied for user 'root'@'localhost' | 数据库密码错误 | 用命令行试一下 mysql -uroot -p ,确认密码 |
| Port 8080 was already in use | 端口被占用 | netstat -ano 找到占用的PID,结束进程,或改server.port |
| java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeFactory | JDK11+缺少JAXB模块 | SpringBoot 2.x项目配置JDK11时引入jaxb-api依赖 |
| Caused by: java.sql.SQLException: The server time zone value | 数据库时区不识别 | 在JDBC URL后加 serverTimezone=Asia/Shanghai |
| Package javax.servlet does not exist | 缺少servlet-api依赖 | 引入 spring-boot-starter-web 后清理并重新导入依赖 |
有一个很典型的坑值得展开说:很多人下载的项目源码在自己电脑上死活启动不了,报 Invalid value type for attribute 'factoryBeanObjectType' 或者各种类找不到。这通常是依赖版本冲突。SpringBoot 3.x的源码不能直接换JDK8跑,2.x的源码用JDK17跑虽然能启动但可能有 CGLIB 警告。最稳妥的办法是保持源码自带的SpringBoot版本不动,装一个对应的JDK版本。
5.2 MyBatis-Plus使用中的隐藏陷阱
MyBatis-Plus虽然好用,但它的“自动化”在某些时候也会坑人。
第一个坑是自动填充不生效。 create_time 、 update_time 这种字段如果在插入时不赋值,数据库里会是null。MP提供了 MetaObjectHandler 接口,正确用法是写一个实现类并注册成Bean,然后在实体字段上加上 @TableField(fill = FieldFill.INSERT) 注解。如果加了注解还是没生效,检查一下是不是在实体类里把这些字段定义到了公共父类中,而父类的包路径没被MP扫描到。
第二个坑是分页失效。很多人以为引入了 mybatis-plus-boot-starter 就有分页了,实际上还需要手动配置分页插件:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
没配置这个Bean的话, selectPage 返回的total永远是0,但查出来的记录数还正常,这种“半失效”最让人头大。排查方式很简单——SQL日志里如果没出现 LIMIT 关键字,就是分页插件没生效。
第三个坑是逻辑删除和唯一索引冲突。如果我给 asset_code 字段建了唯一索引,逻辑删除后再次录入相同编号的资产就会报DuplicateEntry。解决办法是用“无效化编码”策略,比如删除时把资产编号改成一串带时间戳的字符串,或者像支付宝那样附加 -DEL-123456 后缀。这也是为什么很多生产系统里“删除”更像“作废”的原因。
5.3 前后端联调时的跨域与日期序列化
如果你选了前后端分离,跨域问题几乎一定会遇到。浏览器的同源策略会拦截跨域请求,后端需要配置CORS。
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
注意 allowCredentials(true) 时 allowedOrigins("*") 会冲突,必须用 allowedOriginPatterns("*") ,这是很多CORS配置不生效的隐藏原因。另外,如果后端配置了JWT拦截器,要记得对 OPTIONS 预检请求放行,否则前端频繁看到“CORS error”。
日期序列化是另一个几乎必踩的坑。Java后端的 LocalDateTime 默认序列化出来是 2024-06-01T10:30:00 这种带T的格式,前端显示出来很丑。在配置类里统一加一个Jackson配置:
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
};
}
加上之后,所有接口返回的日期格式就统一了,前端接收时不用再写一堆 dayjs 的格式化逻辑。
5.4 安全相关:防SQL注入与防越权操作
固定资产管理系统虽然不是高价值攻击目标,但作为毕设,安全这块是加分项,而且这些知识在面试中也常被问。
SQL注入防护 。MyBatis的 ${} 是字符串拼接,有注入风险; #{} 是预编译,安全。写SQL时原则是:能用 #{} 的地方绝对不用 ${} 。动态排序列名或表名时, ${} 避不开,这时候必须做白名单校验。比如前端传 sortField 时,后端用一个Map映射允许排序的字段,不在Map里的直接拒绝。
越权访问防护 。JWT解决的是“你是你”的问题,解决不了“你能不能做这件事”的问题。如果只校验登录状态而不校验角色,一个普通用户直接调用 /api/asset/delete 接口就能删资产,这就是水平越权。合理的做法是在Mapper查询时加上“当前用户数据范围”条件,比如普通用户只能查到自己名下的资产,资产管理员能查全部门的资产,系统管理员能查全部。用一个简单的数据权限注解配合拦截器解析,是性价比最高的方案。
XSS防护 。输入型XSS的解决思路是全局过滤:继承 HttpServletRequestWrapper ,把请求参数里的 <script> 、 javascript: 等危险内容转义或剔除。Spring Boot里可以通过 @ControllerAdvice 处理请求体中的JSON参数,也可以用现成的过滤库。这里有一个很现实的坑:如果在数据库中存的已经是被转义的 < ,显示在页面上又会变成双重转义的奇怪内容。正确做法是“存原始内容,输出时转义”,或者在保存时统一去标签。要记住这个思路,否则会在“到底在哪一层做过滤”上反复折腾很久。
6. 论文写作要点、系统测试与答辩准备
6.1 论文结构与技术路线图规划
论文结构有标准模板,不要自己乱发明。一篇合格的毕设论文通常包含:摘要(中英文)、绪论、需求分析、系统设计、数据库设计、系统实现、系统测试、总结与展望。如果你们学校有模板,严格按模板来;没有模板的话,这样的结构最保险。
绪论 写三节:项目背景与意义、国内外研究现状、论文组织结构。背景部分不用写太宏大,就把“中小企业固定资产管理依赖Excel台账、流程不透明、数据孤岛”这几个痛点讲清楚就行。研究现状要有参考文献支撑,重点提一下固定资产管理系统从单机版C/S结构向B/S结构、云原生发展的趋势。
需求分析 是重中之重,论文里最忌讳只贴几张截图然后说“这个功能很简单”。正确的写法是:每个核心业务场景写一段文字描述,配套绘制用例图和流程图。比如领用场景,用文字描述“用户登录系统后发起领用申请,填写申请数量和预期归还时间,部门主管登录后看到待审批列表,点击审批通过后资产管理员进行出库登记”,再画一张领用业务流程图。这个过程把“用例描述、业务流程图、时序图”三件套做全,需求分析基本就稳了。
技术路线图 建议画三张:系统总体架构图(B/S架构的浏览器、Web服务器、数据库服务器三层结构)、系统功能结构图(按模块树形展开)、系统技术架构图(体现SpringBoot、MyBatis-Plus、MySQL、前端技术栈之间的关系)。这几张图不仅论文里需要,答辩PPT里也是核心素材。
6.2 系统测试用例与测试报告撰写
很多同学写完代码后根本不测,直接编测试章节,这是非常危险的。答辩评委随机点开一个功能演示,发现Bug,整个测试章节的可信度就崩塌了。建议至少做一轮完整的手工回归测试,把主要功能都点一遍并截图保存。
测试用例表格式换成这样:
| 用例编号 | 功能描述 | 操作步骤 | 预期结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|---|
| TC001 | 用户登录 | 输入正确用户名和密码,点击登录 | 登录成功,跳转系统首页 | 登录成功,跳转首页 | 通过 |
| TC002 | 用户登录 | 输入错误密码 | 提示用户名或密码错误 | 提示用户名或密码错误 | 通过 |
| TC003 | 资产新增 | 填写完整资产信息,点击保存 | 资产列表出现新资产,编号自动生成 | 列表出现新资产,编号为BG2024060003 | 通过 |
| TC004 | 资产删除 | 删除状态为“使用中”的资产 | 提示不可删除 | 提示该资产当前正在使用不可删除 | 通过 |
| TC005 | 领用流程 | 普通用户提交领用申请 | 记录状态为待审批,资产状态不变 | 待审批,资产状态为在库 | 通过 |
| TC006 | 领用审批 | 主管审批通过 | 记录状态为已通过,资产状态变为使用中 | 已通过,资产状态变为使用中 | 通过 |
性能测试可以不发“压测报告”这种大词,简单做一个500条资产数据的分页查询耗时统计,用 System.currentTimeMillis() 打印每次查询耗时,记到表格即可。评委关心的是你有没有性能意识,不是性能指标了不了解压测工具。当然如果你想更专业一点,可以用JMeter压一个接口,导出报告截图贴在论文里,这是加分项。但要注意,如果项目里没有做任何缓存、索引优化,压测结果太漂亮反而不可信,保持数据的真实性比什么都重要。
测试章节写完后,记得在论文最后附上“系统开发与运行环境”一节,把JDK版本、MySQL版本、IDE版本、操作系统版本写清楚。这看似不起眼,但能给评委一个“这个人确实把环境搭建起来并跑通了”的直观印象。
6.3 答辩高频提问与应答思路
答辩时评委问的问题通常围绕几个方向:为什么这么设计、某个功能怎么实现、遇到什么困难、如果是更大规模场景怎么办。把以下问题提前准备充分,基本就稳了:
“你为什么选SpringBoot?” 回答思路:SpringBoot简化了Spring的配置,内置服务器支持快速启动,约定大于配置的机制提升了开发效率,生态丰富,适合快速构建中小型管理系统。这个回答比“因为大家都用”要专业得多。
“你的系统权限是怎么实现的?” 回答思路:RBAC模型,用户关联角色,角色关联权限码,登录后发放JWT,拦截器校验Token并解析角色,Controller使用自定义注解做接口级鉴权。建议把职责链思路讲清楚,评委就会觉得这不是背出来的。
“你如何防止SQL注入?” 回答思路:使用预编译SQL、禁止拼接字符串、使用MyBatis的 #{} 、动态排序列名做白名单校验。这几句话把原理和实战都覆盖了。
“如果你的系统超过一万条数据,哪些地方会变慢?怎么优化?” 回答思路:资产列表多条件查表会慢,方案是给常用条件字段加索引、分页查询减少数据量、热点数据加Redis缓存;报表统计慢,方案是预聚合表或定时生成统计结果。这个问题不要只会说“加索引”,要把场景和方案绑定起来。
“你的系统相比Excel台账,核心价值是什么?” 回答思路:核心价值是把流程规范化,资产全生命周期有迹可循;权限分级,责任明确;统计报表实时可见。用这个回答收尾,能给评委留下一个“真的做过需求理解”的印象。
6.4 项目扩展方向与个人建议
系统做完、论文写完,如果你还有余力,想让它看起来更“高级”,有三条扩展方向。第一是引入Redis缓存,把资产分类、部门列表这些不常变动的字典数据缓存起来,减少数据库压力,论文里能写“缓存策略设计”。第二是在报废审批中接入简单的消息通知,用WebSocket或者轮询接口实现站内提醒,让“待处理事项”主动推送给审批人。第三是给系统加一个简易移动端页面,用H5适配手机浏览器,哪怕只是一个扫码查看资产详情页,也比没有强。
但一定要记住:扩展的前提是核心功能稳定、论文整体逻辑完整。不要因为加了新技术导致系统跑不起来,那就得不偿失了。
我个人在实际开发这类项目时最大的心得体会是:写一个完整系统,真正难的不是某一项技术,而是把业务流程吃透、把状态流转设计正确、把异常情况考虑周全。固定资产管理系统的核心就是那张资产状态机的图,只要状态流转清晰,代码、数据库、接口设计都会顺理成章。你把这个系统完整做完后,无论是对SpringBoot的理解、数据库设计能力还是排查问题的经验,都会有一个质的提升。如果开发过程中遇到具体问题,欢迎随时交流。
&spm=1001.2101.3001.5002&articleId=164181723&d=1&t=3&u=c20f4cc285da49ef921bd1a7bb65bdd1)
889

被折叠的 条评论
为什么被折叠?



