ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

高校教材订购管理系统:Spring Boot + MyBatis 从解压到跑通

高校教材订购管理系统:Spring Boot + MyBatis 从解压到跑通 简介高校教材订购管理系统毕业设计资源面向计算机相关专业学生与需要完成课程设计的开发者围绕教材采购、订单处理、库存管理等核心业务提供完整工程实现。压缩包共398个文件约5.62MB涵盖118个Vue前端组件、87个Java后端类、35个HTML页面、31个JavaScript脚本、15个XML配置以及图片、样式、演示音视频等素材前后端结构清晰便于对照学习。目前已有124人学习下载。通过该资源可获得完整的系统源码与配套文件包括用户登录、教材信息管理、订单确认、库存盘点、报表统计等模块的实现思路同时提供演示视频和说明文档有助于快速理解B/S架构项目的开发与部署流程适合作为毕业设计或项目实训的参考基础。1. 高校教材订购管理系统 .zip 到手先做什么把毕业设计当一次小型软件交付来推进“高校教材订购管理系统”这类毕业设计题目在校园里流传最广的形态往往就是CollegeTextbookOrderingManagementSystem.zip这样一个压缩包里面有前端页面、后端接口、数据库初始化脚本运气好还带一份说明文档。拿到压缩包先别急着解压到桌面双击更别急着换技术栈重写。先把压缩包当一次要交付的小项目来对待确认解压后工程结构、数据库脚本是否齐全、演示入口在哪里。把这三件事理清楚系统跑起来只是时间问题。后面要写论文或者做答辩演示也能直接复用这套梳理结果。适合的人很明确想拿这个题目做毕业设计、课程设计或者刚接手别人代码但不想翻车的同学。2. 先定边界再写代码三种角色、六张核心表教材订购系统的数据模型怎么搭在动任何代码之前先把系统边界画清楚。教材订购系统的业务流程并不复杂每学期开学班级按课程需要订购教材任课教师发起订购单管理员审核后安排出库学生最终领到书。但“不复杂”不等于“可以随便建表”。毕业设计答辩时评委最先翻的就是数据库 E-R 图表关系是否讲得通往往决定第一印象。2.1 三角色权限边界管理员管教材、教师管订购、学生只查单和确认这个系统最常见的角色划分是三套管理员、教师、学生。别为了“功能多”再加一堆角色三角色已经能覆盖完整闭环而且写权限控制时思路最清楚。学生端只管两件事查看可订购教材列表以及查看自己班级的订购单进度。学生一般不需要有“下单”权限因为教材订购是按教学班批量发生的不是学生自己零散买书。如果让学生也能提交订单状态机和报表都会乱掉。教师端按班级发起订购单、追加或者取消订购项。这里的粒度是“班级 教材”不是“学生 教材”。教师选择任课班级系统自动带出该班本学期课程对应的教材清单教师勾选后生成订购单。管理员端功能最多教材信息增删改查、库存维护、订购单审核、出库登记、按学期统计订购量和实发量还有用户和班级的初始化。一次完整的订购闭环从教师发起开始到管理员确认出库结束学生只是被通知方。2.2 六张核心表的字段设计与外键关系订单主表和明细为什么要拆开数据模型按“最少可运行”来设计六张表足够用户表、班级表、课程表、教材表、订购单主表、订购单明细表。如果需要展示统计能力再加一张“教材出库记录表”也能说得过去但六张表已经能支撑答辩中的大多数提问。用户表统一存放管理员、教师、学生的登录信息用role字段区分类型。班级表记录班级名称、所属学院和专业。课程表关联班级和教材表示“某班某课程使用哪本教材”。订购单主表记录订单编号、发起教师、目标班级、总金额、状态订购单明细表记录该订单下每一本教材的数量和单价。表名核心字段说明sys_userid, username, password, role, class_idrole 取 admin / teacher / studentt_classid, class_name, department, grade班级基本信息t_courseid, course_name, class_id, textbook_id课程与班级、教材的关联t_textbookid, book_name, isbn, author, publisher, price, stock教材库存和价格t_orderid, order_no, class_id, teacher_id, status, total_amount订单主表状态字段放这里t_order_itemid, order_id, textbook_id, quantity, price明细表记录当时成交价教材价格必须冗余到t_order_item里不能只关联教材表再实时查价格。因为教材价格每学期可能调整订单一旦生成价格就应当被冻结。这个设计在答辩时可以直接回答“为什么订单明细里有 price 字段”的提问属于典型的数据冗余但冗余得有理有据。2.3 订单状态用字段还是流程图为什么说“状态机”是毕业设计最稳的选型订单状态是在一个状态字段里用数字表示还是单独建一张流程记录表很多毕设选型会纠结这个。我的建议是用状态字段不要碰工作流引擎。单独建流程表确实更“高级”但会引入流程节点表、审批记录表、操作人字段代码量翻倍。毕业设计的核心目的是把教材订购业务讲清楚而不是复刻一个 OA 审批系统。用status字段配合几个固定状态值逻辑一眼能看懂答辩也好解释。状态取值建议0 待审核1 已审核2 已出库3 已取消。教师提交订单后进入 0管理员审核通过变成 1出库登记后变成 2。学生端只需要判断 status 为 1 或 2 就可以显示“教材已安排/已领取”。这个状态机总共只有四个分支写起来不会出错改起来也方便。3. 用 Spring Boot MyBatis 把订购主流程跑通从建库脚本到订单生成的最小闭环技术栈方面最常见也最稳妥的组合是 Spring Boot MyBatis MySQL前端直接用 Thymeleaf 模板引擎省去前后端分离的跨域和打包成本。如果你拿到的压缩包里是 JSP Servlet 或者 SSM 结构也一样能跑但下面这套实现思路可以直接迁移过去。3.1 初始化数据库建库、建表、插入演示数据的三段式 SQL先把数据库准备出来。以下 SQL 是最小可运行版本覆盖教材、下单、明细三张核心表。班级和课程表按同样方式补充即可。-- 教材表库存字段用于出库时扣减 CREATE TABLE t_textbook ( id BIGINT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(200) NOT NULL, isbn VARCHAR(50) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0 ); -- 订单主表teacher_id 是 sys_user 表的外键指向教师账号 CREATE TABLE t_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, class_id BIGINT NOT NULL, teacher_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0, total_amount DECIMAL(10,2) NOT NULL DEFAULT 0, create_time DATETIME NOT NULL ); -- 订单明细表order_id 和 textbook_id 都是外键 CREATE TABLE t_order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, textbook_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 1, price DECIMAL(10,2) NOT NULL );执行顺序有讲究先删后建保证脚本可以重复执行。很多压缩包里的 SQL 脚本没有加DROP TABLE IF EXISTS导致第二次运行时直接报表已存在。建议在任何CREATE TABLE前补一句DROP TABLE IF EXISTS 表名;否则调试时改一次表结构就要手动清库非常痛苦。插入演示数据时教师账号的id、班级的id、教材的id要保持一致。演示数据不要用随机中间乱码要让外键关系肉眼可读比如教师 id 固定为 2班级 id 固定为 1教材 id 从 1 开始递增。3.2 后端分层Controller、Service、Mapper 三段式下单流程的代码最短路径Spring Boot 项目拿到手后先别急着到处乱翻直接顺着“用户登录 → 教师选教材 → 创建订单”这条路找代码。下面这段是OrderService.createOrder()的常见写法核心逻辑集中在事务方法里。Service public class OrderService { Resource private OrderMapper orderMapper; Resource private OrderItemMapper orderItemMapper; /** * 创建订购单一次插入主表循环插入明细表 * param classId 目标班级 id * param teacherId 当前登录教师 id * param textbookIds 勾选的教材 id 列表 */ Transactional(rollbackFor Exception.class) public Long createOrder(Long classId, Long teacherId, ListLong textbookIds) { TOrder order new TOrder(); order.setOrderNo(BK System.currentTimeMillis()); order.setClassId(classId); order.setTeacherId(teacherId); order.setStatus(0); order.setTotalAmount(BigDecimal.ZERO); orderMapper.insert(order); // 明细表要循环插入这里不能省略不能用一条 insert 带多条 values for (Long textbookId : textbookIds) { TTextbook tb textbookMapper.selectById(textbookId); TOrderItem item new TOrderItem(); item.setOrderId(order.getId()); item.setTextbookId(textbookId); item.setQuantity(1); item.setPrice(tb.getPrice()); orderItemMapper.insert(item); } // 重新计算总金额避免前端传参被篡改 BigDecimal total orderItemMapper.sumAmountByOrderId(order.getId()); orderMapper.updateTotalAmount(order.getId(), total); return order.getId(); } }Transactional是这里最关键的一行。如果明细插入到一半抛异常主表订单会回滚不会产生“有订单但没明细”的脏数据。第一次跑通后可以故意传一个不存在的 textbookId 试一次事务回滚答辩时如果被问到“事务怎么体现的”这就是现成素材。注意totalAmount不要在前端算好直接传要在后端根据明细重新汇总。这既是安全问题也是答辩时能讲清楚的一个细节。教师只能勾选教材金额由后端从教材表读取计算前端改价格没有意义。3.3 状态流转教师提交、管理员审核、出库登记的三个关键更新语句下单之后系统里就是三个状态流转动作。写起来最稳妥的方式是三个独立的方法不要写成一个万能 update 方法。教师提交订单实际就是插入订单时状态为 0这一步在createOrder里已经完成。管理员审核通过时执行以下更新-- 审核通过状态 0 - 1记录审核时间 UPDATE t_order SET status 1, audit_time NOW() WHERE id #{orderId} AND status 0;WHERE条件里带status 0是一个很好的习惯。它保证只有待审核的订单能被审核已经审核的订单不会被二次操作。如果更新返回影响行数为 0说明订单状态已被其他操作修改Service 层可以主动抛出异常。这就是乐观锁的简易版本不用引入 version 字段就能解决问题。出库登记时除了更新订单状态为 2还要扣减教材库存。这两件事必须放在同一个事务里。库存扣减的 SQL 要加上库存充足的条件-- 出库库存足够才扣减否则影响行数为 0 UPDATE t_textbook SET stock stock - #{quantity} WHERE id #{textbookId} AND stock #{quantity};如果影响行数为 0直接抛异常回滚订单状态变更防止出现“订单已出库但库存没减”的 bug。这种问题在现场演示时最容易翻车因为演示数据如果被反复操作库存很容易变成负数。3.4 分页查询与汇总报表PageHelper 和聚合 SQL 的取舍教材列表、订单列表几乎都要分页。手写 LIMIT 分页不难但 MyBatis 项目里用 PageHelper 是最省事的做法。引入依赖后Service 层只要在查询前设置页码和每页条数。// 分页参数页码从 1 开始每页 10 条 PageHelper.startPage(pageNum, pageSize); ListOrderVO list orderMapper.selectOrderPage(condition); PageInfoOrderVO pageInfo new PageInfo(list);PageHelper.startPage()之后紧跟的第一条查询语句会被自动拼上 LIMIT前提是紧跟的那行不能有任何其他数据库操作。我见过有人在这中间写了日志查询结果分页加到了错误的 SQL 上页面数据全乱。汇总报表不需要分页直接用聚合查询。统计每个教材的总订购量时SQL 如下select idselectBookSummary resultTypemap SELECT b.book_name, SUM(oi.quantity) AS total_quantity FROM t_order_item oi JOIN t_textbook b ON oi.textbook_id b.id GROUP BY b.book_name ORDER BY total_quantity DESC /select这条 SQL 用于管理员端的“教材订购统计”页面。答辩时如果能现场讲清楚为什么要 JOIN 教材表而不是直接存书名以及为什么用 SUM 而不是在 Java 里循环累加基本就能证明 SQL 基础是过关的。4. 从 zip 解压到外键删除教材订购系统调试现场最常翻车的 5 个坑这部分是血泪经验。每年毕业设计季都会有人卡在同样几个问题上而且每个问题都在答辩现场暴露过。按“现象 → 原因 → 解决”逐个说。4.1 压缩包解压后工程结构对不上伪加密、中文路径、zip 解压软件的连锁问题现象拿到CollegeTextbookOrderingManagementSystem.zip后右键解压报“文件损坏”或者“密码错误”换了解压软件又出现乱码文件名。原因网上流转的 zip 有一部分带伪加密标记文件本身没加密但 zip 的加密位被改了普通解压工具会误判。另外压缩包如果是在 Windows 下打出来的文件名常见 GBK 编码项目里的 Java 文件或配置文件路径一旦含中文Spring Boot 启动时可能直接报ClassNotFoundException。解决别用系统自带的右键“压缩为 zip / 解压”功能换 7-Zip 这类工具解压时选择“保留文件路径”并把目标目录改为纯英文比如D:\textbook-system。解压后第一件事不是打开 IDE而是检查顶层目录是不是套了两层文件夹——很多包解压出来是CollegeTextbookOrderingManagementSystem/CollegeTextbookOrderingManagementSystem/如果直接用 IDE 打开外层Maven 根本识别不到 pom.xml。4.2 本地服务起不来或页面 404端口被占用与 context-path 不一致现象Spring Boot 启动日志显示Port 8080 was already in use或者启动成功但浏览器访问localhost:8080永远显示 404。原因8080 端口经常被其他 Java 进程、管理后台、甚至微信开发者工具占用。404 则多半是项目配置了server.servlet.context-path比如/textbook但你访问时没带上这个前缀。解决先查端口占用Windows 用netstat -ano | findstr 8080Linux/macOS 用lsof -i:8080找到 PID 后确认不是杀毒软件或系统进程再结束。然后打开application.yml看有没有context-path配置server: port: 8080 servlet: context-path: /textbook如果存在这行配置所有接口路径都必须带/textbook前缀前端页面的链接也要对应修改。演示机上我建议直接把context-path注释掉少一个坑多一份安心。4.3 MySQL 8.x 版本连接失败驱动版本、时区、认证方式三重问题现象项目在别人电脑上能跑到你电脑上报Access denied for user或者The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。原因压缩包里的连接配置一般是针对 MySQL 5.7 写的驱动还是com.mysql.jdbc.Driver。装了 MySQL 8.x 之后驱动类名变了时区参数必须显式指定默认认证方式也改成了caching_sha2_password。解决确认 MySQL 版本后把驱动升级到 8.x并在 JDBC 连接串里加两个参数spring: datasource: url: jdbc:mysql://localhost:3306/textbook_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password driver-class-name: com.mysql.cj.jdbc.DriverserverTimezone要放在 URL 上代码里设置是无效的。allowPublicKeyRetrievaltrue是针对 MySQL 8.x 认证插件的必填参数不加会在第一次连接时报Public Key Retrieval is not allowed。这两个参数演示前先确认一遍可以少熬夜。4.4 登录成功后页面跳回登录页拦截器把静态资源也拦了现象登录页输入账号密码点击登录后确实调到了/index但刷新一下就又跳回登录页。打开浏览器调试面板发现 CSS、JS、图片全部加载失败接口返回 401。原因登录拦截器写了/ **拦截所有路径但没有放行静态资源和登录接口。静态资源加载失败后页面渲染异常看起来就像“登录无效”。解决在拦截器注册处明确放行白名单。Spring Boot 的常见写法是registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /css/**, /js/**, /images/**, /error);/error也建议放行否则 Spring Boot 默认错误页会被拦截器拦截报错信息变成一片空白。判断登录状态时优先从 Session 获取当前用户不要从 Cookie 里直接读用户名防止演示机浏览器残留旧登录信息。4.5 删除教材报外键约束失败物理删除和逻辑删除的选择现象管理员在教材列表页删除一本教材页面弹出Cannot delete or update a parent row: a foreign key constraint fails。原因教材表被课程表或者订单明细表引用物理删除在 InnoDB 外键约束下不允许执行。解决最简单的方式是给教材表加一个deleted字段用逻辑删除代替物理删除。所有查询语句统一加WHERE deleted 0。不要为了图省事把外键约束删掉那样答辩时被问到“数据完整性怎么保证”会很难圆回来。也可以在删除前先写一个统计接口判断该教材是否已经被订单引用若有引用就提示“该教材已产生订购记录不允许删除”改用下架操作。两种方案都比硬删好。5. 答辩演示脚本把数据固化到“一键重置”三分钟演示不靠运气毕业论文答辩的现场演示环节最常见的意外是演示数据被之前某次操作改坏了页面一刷新就空空如也。这里给出一个我一直在用的习惯专门解决这类问题。5.1 写一个一键重置脚本把数据库恢复到初始状态准备一个reset.shWindows 下对应reset.bat内容只有三件事先删库再用初始化 SQL 重建重启后端应用。这样无论演示前数据被折腾成什么样只要双击脚本一分钟回到干净状态。mysql -uroot -p123456 sql/01_drop_and_create.sql mysql -uroot -p123456 textbook_db sql/02_init_data.sql mvn spring-boot:run不要用 IDE 手动一条条执行 SQL那是排练时干的事。正式答辩前把脚本完整跑一遍确认从零到项目启动只需要这一个命令。这个脚本本身也可以在答辩时展示属于“部署自动化”的加分项。5.2 演示操作顺序固定下来先展示报表再展示下单最后展示状态流转演示顺序建议固定成三分钟闭环。先打开“教材订购统计”页面展示报表因为图表类页面视觉冲击力强学生一眼能看出系统有实际数据。然后切到教师账号选一个班级提交订购单展示状态从待审核变成已审核。最后切到管理员账号审核通过并到库存页面确认库存已经扣减。每次演示都要用同一批账号、同一批数据不要现场临时改数据。我把当时的教训简化成一句话演示系统最怕的不是功能少而是状态不可控。做一套固定数据比多做几个页面更有用。希望这个习惯也能帮到准备答辩的你。本文还有配套的精品资源点击获取
返回列表