毕业设计实战:基于SpringBoot+MySQL的校园资产管理系统,从0到1全流程拆解,数据库设计到功能测试一文搞定!

47 阅读13分钟

毕业设计实战:基于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. 开发环境搭建(一步一图)

很多同学卡在环境配置,跟着这个顺序来:

  1. 装JDK 11:Oracle官网下载,配置JAVA_HOME,Path添加%JAVA_HOME%\bin
  2. 装IDEA 2023+:社区版够用,安装时勾选SpringBoot插件
  3. 装MySQL 8.0:用MySQL Installer安装,记得root密码,用Navicat Premium 16连接
  4. 初始化SpringBoot项目
    • IDEA新建SpringBoot项目,选2.7.x版本
    • 勾选Web、MySQL、MyBatis、Thymeleaf依赖
    • 配置application.yml:数据库连接、端口、Thymeleaf缓存关掉(开发时热更新)
  5. 测试连接:启动项目,访问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个核心实体

  1. 用户表(user):系统使用者(管理员、普通用户)
  2. 资产表(asset):资产基本信息(名称、编号、类型、库存、存放位置)
  3. 入库表(stock_in):资产入库记录(入库单号、入库时间、操作人)
  4. 入库详情表(stock_in_detail):入库单与资产的关联表(资产ID、数量)
  5. 借用表(borrow):资产借用记录(借用单号、借用人、借用时间、归还状态)
  6. 维修表(maintenance):资产维修记录(维修单号、维修数量、维修原因)
  7. 报废表(scrap):资产报废记录(报废单号、报废数量、报废原因)
  8. 字典表(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个必过技巧

  1. 演示流程要完整:资产入库→用户借用→管理员审核→用户归还→维修登记→报废申请,展示完整的资产生命周期
  2. 重点展示“库存一致性”:演示借用、归还、维修时库存的实时变化,用实际数据证明系统逻辑正确
  3. 准备Q&A
    • Q:为什么用SpringBoot不用SSM?
      A:SpringBoot简化配置,快速开发,适合毕设时间紧的特点
    • Q:数据量大怎么办?
      A:资产表按类型分表,借用记录按时间归档,查询加索引
    • Q:如何保证数据安全?
      A:SQL防注入(MyBatis参数绑定)、XSS过滤、权限控制(Spring Security)

最后:给你的毕设加油包

按照这个流程走,校园资产管理系统的毕设肯定能顺利完成!需要完整源码(带详细注释)、数据库脚本(含测试数据)的同学,评论区告诉我。

记住:毕设不求炫技,但求完整、稳定、逻辑清晰。点赞收藏这篇,做毕设时随时回来看看,少走弯路!

祝大家毕设顺利,答辩一次过!🎓