毕业设计实战:基于SpringBoot+MySQL的校园资产管理系统,从0到1全流程拆解,数据库设计到功能测试一文搞定!
当初做校园资产管理系统毕设时,光“资产借用”和“维修记录”的表关联就卡了一周——一开始没做库存联动,资产被借出后库存没减少,还能继续借,导师测试时直接发现库存数据混乱!后来重做了三遍数据库设计才摸清门道。今天把需求分析、技术选型、数据库设计到测试的完整流程全分享,学弟学妹们不用再踩我踩过的坑!
一、先搞明白“校园资产管理系统要管啥”!需求别跑偏
刚开始我以为就是个简单的增删改查,结果导师说“要有完整的资产生命周期管理”——从入库、借用、维修到报废,每个环节都得有记录!后来才明白,需求分析得抓住“资产从哪儿来、到哪儿去、状态如何”这条主线。
1. 核心用户&功能拆解(经验总结版)
系统主要两类用户:管理员和普通用户(学生或教职工):
- 管理员端(核心功能):
- 资产管理:资产信息维护(新增/编辑/删除/查看详情)、资产分类管理(办公设备/实验器材等)、存放地点管理、上传资产照片
- 流程管理:入库管理(新增入库单、关联资产、记录数量)、借用审核(审核借用申请、标记归还状态)、维修管理(登记维修记录、更新维修状态)、报废管理(提交报废申请、记录报废原因)
- 基础数据:部门管理(新增/编辑部门信息)、用户管理(维护用户信息)、字典管理(资产类型、存放地点等)
- 用户端(实用功能):
- 资产查询:浏览资产列表(按分类筛选)、查看资产详情(库存量、存放位置)、搜索资产
- 业务申请:提交借用申请(选择资产、填写数量、借用事由)、查看我的借用记录(待审核/已通过/已归还)、查看维修记录
- 个人信息:修改个人资料、查看申请进度
2. 需求分析避坑指南(真实教训!)
- 别忽视“库存联动”:我最开始做借用功能时,只记录了借用关系,没减少资产库存,导致同一资产能被无限次借用!后来加上了“借用时减库存,归还时加库存”的逻辑才正确。
- 一定要画“资产状态流转图”:用DrawIO画一个状态图:新资产→入库→在库→借用中→维修中→报废。答辩时展示这个图,导师一眼就看出你理解了业务逻辑。
- 写清楚“约束条件”:比如“借用数量不能超过当前库存”“报废需要填写原因”“维修中的资产不能借用”。编码时对着这些约束写校验逻辑,不会遗漏。
3. 可行性分析要具体!3个角度写清楚
导师最爱问“你这系统现实中能用吗”,别光说“我觉得可以”,从这3点展开:
- 技术可行性:SpringBoot、MySQL、MyBatis都是成熟技术,学习资料多,社区活跃,遇到问题容易找到解决方案。
- 经济可行性:开发工具全部免费(IDEA社区版、MySQL、Navicat试用版),部署可以用学校服务器或学生云服务器优惠,成本极低。
- 操作可行性:界面参考图书馆借阅系统,操作流程直观。我找同学测试,2分钟就学会了借用申请和查看记录。
二、技术选型求稳不求新!这套组合拳够用
看到网上各种新技术心痒痒?打住!毕设最重要的是“稳定完成”,不是“技术炫技”。我最初想用Vue3+SpringCloud,结果微服务配置卡了三天,最后换回SpringBoot 2.7 + MySQL 8.0 + MyBatis-Plus + Thymeleaf,开发效率直接翻倍!
1. 技术栈详细对比(附选择理由)
| 技术工具 | 为啥选它 | 避坑提醒! |
|---|---|---|
| SpringBoot 2.7 | 简化配置,内嵌Tomcat,快速搭建,社区资源丰富 | 别用3.0+!部分依赖兼容性还不够稳定 |
| MySQL 8.0 | 事务支持完善,性能稳定,支持JSON类型,utf8mb4编码 | 安装时一定设utf8mb4!不然资产名称带emoji或生僻字会乱码 |
| MyBatis-Plus | 简化CRUD,提供Wrapper条件构造器,减少SQL编写 | 别过度依赖自动生成,复杂查询还是手写XML更清晰 |
| Thymeleaf | 天然支持Spring,表达式简单,适合后端渲染 | 别混用JSP和Thymeleaf,模板引擎冲突会让你头大 |
| Bootstrap 5 | 响应式组件丰富,页面搭建快,兼容性好 | 用CDN引入,别下载到本地,减少项目体积 |
2. 开发环境搭建(一步一图)
很多同学卡在环境配置,跟着这个顺序来:
- 装JDK 11:Oracle官网下载,配置JAVA_HOME,Path添加
%JAVA_HOME%\bin - 装IDEA 2023+:社区版够用,安装时勾选SpringBoot插件
- 装MySQL 8.0:用MySQL Installer安装,记得root密码,用Navicat Premium 16连接
- 初始化SpringBoot项目:
- IDEA新建SpringBoot项目,选2.7.x版本
- 勾选Web、MySQL、MyBatis、Thymeleaf依赖
- 配置
application.yml:数据库连接、端口、Thymeleaf缓存关掉(开发时热更新)
- 测试连接:启动项目,访问
http://localhost:8080看到 Whitelabel Error Page 说明启动成功(因为还没写页面)
3. 项目结构要清晰!答辩加分项
src/main/java
├── com.campus.asset
│ ├── controller # 控制层
│ ├── entity # 实体类
│ ├── mapper # Mapper接口
│ ├── service # 服务层
│ │ ├── impl # 服务实现
│ └── config # 配置类
src/main/resources
├── static # 静态资源
├── templates # 模板页面
├── mapper # MyBatis XML
└── application.yml # 配置文件
用DrawIO画个架构图:浏览器请求→Controller→Service→Mapper→MySQL,返回数据→Thymeleaf渲染→HTML页面。答辩时展示这个,显得很专业。
三、数据库设计:资产状态流转是核心
这部分是系统的“心脏”,设计不好后面全是坑。我花了整整一周才理清“资产-借用-维修-报废”的关系网。
1. 核心实体&关系(附ER图技巧)
8个核心实体:
- 用户表(user):系统使用者(管理员、普通用户)
- 资产表(asset):资产基本信息(名称、编号、类型、库存、存放位置)
- 入库表(stock_in):资产入库记录(入库单号、入库时间、操作人)
- 入库详情表(stock_in_detail):入库单与资产的关联表(资产ID、数量)
- 借用表(borrow):资产借用记录(借用单号、借用人、借用时间、归还状态)
- 维修表(maintenance):资产维修记录(维修单号、维修数量、维修原因)
- 报废表(scrap):资产报废记录(报废单号、报废数量、报废原因)
- 字典表(dict):类型字典(资产类型、存放地点、部门等)
ER图绘制要点:
- 矩形:实体(如“资产”、“借用”)
- 菱形:关系(如“用户-借用”是一对多,“资产-借用”是一对多)
- 椭圆:属性(如资产的“编号”、“名称”)
- 关键关系:一个入库单对应多个入库详情(一对多),一个资产可以被多次借用(一对多)
2. 建表SQL(重点表示例)
资产表(核心中的核心):
CREATE TABLE `asset` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`asset_number` varchar(50) NOT NULL COMMENT '资产编号(唯一)',
`asset_name` varchar(100) NOT NULL COMMENT '资产名称',
`asset_type` int DEFAULT NULL COMMENT '资产类型(关联字典表)',
`storage_location` int DEFAULT NULL COMMENT '存放地点(关联字典表)',
`stock_quantity` int NOT NULL DEFAULT '0' COMMENT '库存数量',
`total_quantity` int NOT NULL DEFAULT '0' COMMENT '总数量',
`asset_image` varchar(255) DEFAULT NULL COMMENT '资产图片路径',
`asset_status` tinyint DEFAULT '1' COMMENT '资产状态(1-正常 2-维修中 3-已报废)',
`description` text COMMENT '资产描述',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_asset_number` (`asset_number`),
KEY `idx_asset_type` (`asset_type`),
KEY `idx_asset_status` (`asset_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='资产表';
借用表(注意状态字段):
CREATE TABLE `borrow` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`borrow_number` varchar(50) NOT NULL COMMENT '借用单号',
`user_id` int NOT NULL COMMENT '借用人ID',
`asset_id` int NOT NULL COMMENT '资产ID',
`borrow_quantity` int NOT NULL COMMENT '借用数量',
`borrow_reason` varchar(500) DEFAULT NULL COMMENT '借用事由',
`borrow_time` datetime DEFAULT NULL COMMENT '借用时间',
`expected_return_time` datetime DEFAULT NULL COMMENT '预计归还时间',
`actual_return_time` datetime DEFAULT NULL COMMENT '实际归还时间',
`borrow_status` tinyint NOT NULL DEFAULT '1' COMMENT '借用状态(1-待审核 2-已通过 3-已拒绝 4-借用中 5-已归还)',
`approval_comment` varchar(500) DEFAULT NULL COMMENT '审核意见',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_borrow_number` (`borrow_number`),
KEY `idx_user_id` (`user_id`),
KEY `idx_asset_id` (`asset_id`),
KEY `idx_borrow_status` (`borrow_status`),
CONSTRAINT `fk_borrow_asset` FOREIGN KEY (`asset_id`) REFERENCES `asset` (`id`),
CONSTRAINT `fk_borrow_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='资产借用表';
3. 表关联测试SQL
-- 查询某个用户的所有借用记录(带资产信息)
SELECT
b.borrow_number,
a.asset_name,
a.asset_number,
b.borrow_quantity,
b.borrow_time,
b.expected_return_time,
CASE b.borrow_status
WHEN 1 THEN '待审核'
WHEN 2 THEN '已通过'
WHEN 3 THEN '已拒绝'
WHEN 4 THEN '借用中'
WHEN 5 THEN '已归还'
END as status_name
FROM borrow b
JOIN asset a ON b.asset_id = a.id
WHERE b.user_id = 1
ORDER BY b.create_time DESC;
-- 查询某个资产的当前库存(总数量 - 借用中数量 - 维修中数量)
SELECT
a.asset_name,
a.total_quantity as 总数量,
a.stock_quantity as 当前库存,
IFNULL(SUM(CASE WHEN b.borrow_status = 4 THEN b.borrow_quantity ELSE 0 END), 0) as 借用中数量,
IFNULL(SUM(CASE WHEN m.maintenance_status = 1 THEN m.quantity ELSE 0 END), 0) as 维修中数量
FROM asset a
LEFT JOIN borrow b ON a.id = b.asset_id AND b.borrow_status = 4
LEFT JOIN maintenance m ON a.id = m.asset_id AND m.maintenance_status = 1
WHERE a.id = 1
GROUP BY a.id;
四、功能实现:三大核心模块详解
不用面面俱到,重点做好“资产管理”、“借用流程”、“维修报废”这三个模块,答辩足够出彩。
1. 资产管理模块(基础但重要)
(1)关键逻辑
- 新增资产:生成唯一资产编号(规则:ASSET+年月日+4位随机数)
- 图片上传:限制2MB以内,只允许jpg/png,存储路径
/static/upload/asset/{yyyyMMdd}/ - 库存更新:通过入库单增加,借用/维修/报废减少,始终保持
stock_quantity = total_quantity - 借用中 - 维修中 - 已报废
(2)代码示例(资产新增)
@Service
public class AssetServiceImpl implements AssetService {
@Autowired
private AssetMapper assetMapper;
@Override
@Transactional
public Result addAsset(Asset asset, MultipartFile imageFile) {
// 1. 生成资产编号
String assetNumber = "ASSET" +
new SimpleDateFormat("yyyyMMdd").format(new Date()) +
RandomUtil.randomNumbers(4);
asset.setAssetNumber(assetNumber);
// 2. 校验资产编号唯一性
LambdaQueryWrapper<Asset> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Asset::getAssetNumber, assetNumber);
if (assetMapper.selectCount(wrapper) > 0) {
return Result.error("资产编号生成冲突,请重试");
}
// 3. 处理图片上传
if (imageFile != null && !imageFile.isEmpty()) {
if (!imageFile.getContentType().startsWith("image/")) {
return Result.error("只支持图片格式(JPG/PNG)");
}
if (imageFile.getSize() > 2 * 1024 * 1024) {
return Result.error("图片大小不能超过2MB");
}
String datePath = new SimpleDateFormat("yyyyMMdd").format(new Date());
String fileName = UUID.randomUUID() +
imageFile.getOriginalFilename().substring(
imageFile.getOriginalFilename().lastIndexOf(".")
);
String filePath = "/static/upload/asset/" + datePath + "/" + fileName;
// 实际存储到磁盘(这里简写)
// FileUtil.saveFile(imageFile, filePath);
asset.setAssetImage(filePath);
}
// 4. 设置初始状态
asset.setAssetStatus(1); // 1-正常
asset.setStockQuantity(asset.getTotalQuantity()); // 初始库存=总量
// 5. 保存到数据库
assetMapper.insert(asset);
return Result.success("资产添加成功", asset);
}
}
2. 借用流程模块(核心业务)
(1)完整流程
用户提交借用→管理员审核→通过后减库存→用户借用中→用户归还→加库存
(2)代码示例(借用审核)
@Service
public class BorrowServiceImpl implements BorrowService {
@Autowired
private BorrowMapper borrowMapper;
@Autowired
private AssetMapper assetMapper;
@Override
@Transactional
public Result approveBorrow(Integer borrowId, Integer status, String comment) {
// 1. 查询借用记录
Borrow borrow = borrowMapper.selectById(borrowId);
if (borrow == null) {
return Result.error("借用记录不存在");
}
// 2. 状态校验(只能审核待审核的记录)
if (borrow.getBorrowStatus() != 1) {
return Result.error("该记录已审核,不能重复操作");
}
// 3. 更新借用状态
borrow.setBorrowStatus(status); // 2-通过 3-拒绝
borrow.setApprovalComment(comment);
borrow.setBorrowTime(new Date()); // 审核通过时间即借用时间
// 4. 如果审核通过,减少资产库存
if (status == 2) {
Asset asset = assetMapper.selectById(borrow.getAssetId());
if (asset.getStockQuantity() < borrow.getBorrowQuantity()) {
return Result.error("库存不足,当前库存:" + asset.getStockQuantity());
}
// 更新库存
asset.setStockQuantity(asset.getStockQuantity() - borrow.getBorrowQuantity());
assetMapper.updateById(asset);
}
// 5. 保存借用记录
borrowMapper.updateById(borrow);
String msg = status == 2 ? "借用申请已通过" : "借用申请已拒绝";
return Result.success(msg);
}
}
3. 维修报废模块(完整生命周期)
(1)页面设计要点
- 维修登记:选择资产、填写维修数量、维修原因、预计完成时间
- 报废申请:选择资产、报废数量、报废原因(必填)、上传报废证明图片
- 状态联动:资产维修中时,库存要减少,且不能借用
(2)库存同步逻辑
// 维修登记时减少库存
public Result addMaintenance(Maintenance maintenance) {
Asset asset = assetMapper.selectById(maintenance.getAssetId());
// 检查库存是否足够
if (asset.getStockQuantity() < maintenance.getQuantity()) {
return Result.error("库存不足,无法进行维修登记");
}
// 减少库存
asset.setStockQuantity(asset.getStockQuantity() - maintenance.getQuantity());
asset.setAssetStatus(2); // 2-维修中
assetMapper.updateById(asset);
// 保存维修记录
maintenance.setMaintenanceNumber("MT" + new Date().getTime());
maintenance.setMaintenanceStatus(1); // 1-维修中
maintenanceMapper.insert(maintenance);
return Result.success("维修登记成功");
}
// 维修完成时恢复库存
public Result completeMaintenance(Integer maintenanceId) {
Maintenance maintenance = maintenanceMapper.selectById(maintenanceId);
Asset asset = assetMapper.selectById(maintenance.getAssetId());
// 恢复库存
asset.setStockQuantity(asset.getStockQuantity() + maintenance.getQuantity());
asset.setAssetStatus(1); // 1-正常
assetMapper.updateById(asset);
// 更新维修记录
maintenance.setMaintenanceStatus(2); // 2-已完成
maintenance.setCompleteTime(new Date());
maintenanceMapper.updateById(maintenance);
return Result.success("维修完成");
}
五、测试要全面!这4类测试不能少
1. 功能测试用例(示例)
资产借用测试
| 测试场景 | 操作步骤 | 预期结果 | 实际结果 |
|---|---|---|---|
| 正常借用 | 选择资产A(库存10)→借用数量5→提交 | 借用单状态“待审核”,资产库存不变 | |
| 超库存借用 | 选择资产A(库存10)→借用数量15→提交 | 提示“库存不足,当前库存10” | |
| 审核通过 | 管理员审核待审核的借用单→点击“通过” | 借用状态变“借用中”,资产库存减少5 | |
| 审核拒绝 | 管理员审核待审核的借用单→点击“拒绝”→填写原因 | 借用状态变“已拒绝”,资产库存不变 |
2. 库存一致性测试(重点!)
-- 测试脚本:验证库存数据一致性
SELECT
a.asset_number,
a.asset_name,
a.total_quantity as 总数量,
a.stock_quantity as 系统库存,
(a.total_quantity
- IFNULL((SELECT SUM(borrow_quantity) FROM borrow WHERE asset_id = a.id AND borrow_status = 4), 0)
- IFNULL((SELECT SUM(quantity) FROM maintenance WHERE asset_id = a.id AND maintenance_status = 1), 0)
- IFNULL((SELECT SUM(scrap_quantity) FROM scrap WHERE asset_id = a.id), 0)
) as 计算库存
FROM asset a
HAVING 系统库存 != 计算库存;
如果查询结果有数据,说明库存计算有问题!
3. 边界条件测试
- 借用数量为0或负数
- 借用时间早于当前时间
- 图片上传超过2MB
- 输入超长字符串(资产名称超过100字符)
4. 并发测试(加分项)
模拟多个用户同时借用同一资产:
// 使用synchronized或数据库乐观锁保证一致性
@Transactional
public synchronized Result borrowAsset(Borrow borrow) {
// 检查库存
Asset asset = assetMapper.selectById(borrow.getAssetId());
if (asset.getStockQuantity() < borrow.getBorrowQuantity()) {
return Result.error("库存不足");
}
// 更新库存
asset.setStockQuantity(asset.getStockQuantity() - borrow.getBorrowQuantity());
assetMapper.updateById(asset);
// 保存借用记录
borrowMapper.insert(borrow);
return Result.success();
}
六、答辩准备:3个必过技巧
- 演示流程要完整:资产入库→用户借用→管理员审核→用户归还→维修登记→报废申请,展示完整的资产生命周期
- 重点展示“库存一致性”:演示借用、归还、维修时库存的实时变化,用实际数据证明系统逻辑正确
- 准备Q&A:
- Q:为什么用SpringBoot不用SSM?
A:SpringBoot简化配置,快速开发,适合毕设时间紧的特点 - Q:数据量大怎么办?
A:资产表按类型分表,借用记录按时间归档,查询加索引 - Q:如何保证数据安全?
A:SQL防注入(MyBatis参数绑定)、XSS过滤、权限控制(Spring Security)
- Q:为什么用SpringBoot不用SSM?
最后:给你的毕设加油包
按照这个流程走,校园资产管理系统的毕设肯定能顺利完成!需要完整源码(带详细注释)、数据库脚本(含测试数据)的同学,评论区告诉我。
记住:毕设不求炫技,但求完整、稳定、逻辑清晰。点赞收藏这篇,做毕设时随时回来看看,少走弯路!
祝大家毕设顺利,答辩一次过!🎓