
1. 毕设选题为什么盯上“扶贫爱心超市”1.1 一个传统爱心超市的真实痛点扶贫爱心超市这个题目我最初是在毕设选题清单里扫到的第一眼觉得也就是个“进销存换皮”但真正做需求调研时才发现这类公益物资管理场景的痛点远比想象中扎手。我当时实地看过两个线下运营点情况高度一致墙上贴着积分兑换规则货架上摆着米面油和洗衣液但柜台底下压着一摞手写登记本。每一页记着“张三3月12日领大米5kg扣50积分”。单看一条记录没毛病但想盘点库存就得一页一页加想知道哪些物资快过期了没人管困难群众还剩多少积分得靠工作人员回忆捐赠物资进来也只有一张手写收条。这些问题归纳起来有四类物资台账不统一。入库单、领用登记、库存余量散落在不同本子或不同人手里核对一次要耗掉半天。积分发放和核销缺约束。谁发的分、为什么发、扣了多少流程是模糊的事后追溯基本不可能。多角色协同效率低。管理员、志愿者、受助群众之间信息不透明受助群众到店才发现没货或者有货但积分不够。统计报表缺失。月度发放量、物资类别分布、积分核销率这些运营指标靠手工根本算不出来。正因如此“信息化系统”才有明确的用武之地。把物资流转、积分流水、角色权限、统计报表串成一套系统至少能把运营成本降下来把数据留痕做扎实。这个题目最大的优势是它有真实的业务原型不管你面对哪个学校、哪个导师对方都能快速理解你要做什么需求论证环节几乎不会被卡。1.2 从毕设角度评估这个题目的性价比毕设选题最怕三种情况题目太虚讲不清楚做什么技术太老答辩老师觉得没含量功能太杂做到最后烂尾。扶贫爱心超市恰好避开了这三个坑。从技术覆盖面上看它踩中了 Java Web 毕设的标准考点MVC 分层、MyBatis 持久层、数据库设计、事务管理、权限控制、图表统计。难度梯度很友好既有基础功能的“保底分”又有并发控制、状态机设计这类“加分项”。从业务角度看它不是生造的题库项目而是真实存在的公益场景答辩时你能理直气壮地说“我做过业务调研”这比那些通用“XX管理系统”的模板项目有说服力得多。从开发量上做横向对比电商系统的核心流程包含购物车、订单、支付、库存扣减、售后退款随便一个模块就能撑起一篇毕设但关联逻辑多、联调麻烦纯信息管理系统比如新闻发布、公告管理做起来快但技术点单薄爱心超市系统介于两者之间——核心流程清晰入库、领用、积分、统计业务规则不算复杂一个人六周左右能完整拿下还留有余地做界面优化和扩展功能。我的结论很明确这类“公益信息化”的题目在 Java 毕设里属于性价比很高的选择。它有一个明确的核心业务闭环又不会像电商那样把战线拉得太长。2. 系统总体设计从业务流程到技术选型2.1 先理清业务再谈代码很多同学拿到题目第一件事就是装环境写代码这是毕设最常见也是最致命的错误。系统设计的第一张图纸应该是业务流程不是数据库表更不是 Controller 代码。我拿到题目后做的第一件事是把角色和业务动作画清楚。这个系统里有四类核心角色系统管理员负责用户管理、物资审批、数据统计、系统配置。管理人员爱心超市运营者负责物资入库、物资上架、积分发放、领用核销。志愿者可以协助管理物资、查看库存但不具备积分发放权限。受助群众用户端查看物资、查看积分、在线申请兑换、到店领取。业务主链路是社会捐赠或采购入库 → 物资审核上架 → 管理员发放积分 → 用户浏览商品 → 用积分提交兑换申请 → 管理人员核销出库 → 库存扣减、积分扣减、流水记录。这条链路看起来简单但每个环节都有分支。物资入库可能是捐赠入库也可能是采购入库捐赠涉及保质期登记积分可能是定期定额发放也可能是一次性活动奖励兑换申请可能被驳回驳回后积分要退回。这些分支在设计阶段就要全部列出来落到数据库和接口上否则后面半路加需求才是真折磨。我建议动笔写代码之前至少要产出三张图角色权限矩阵每个角色能操作哪些菜单、哪些接口。业务流程图从物资入库到领用完成的完整链路标注每个节点的操作人。状态流转图物资的上下架状态、兑换单的状态、账户积分的状态。这三张图花不了多少时间但对系统清晰度和答辩质量帮助极大。答辩时把这三张图往 PPT 上一放老师基本能确认你是真做过东西不是拿别人的代码改个标题。2.2 技术选型Spring Boot MyBatis 的标准打法Java 毕设的技术栈没什么悬念Spring Boot 已经是绝对主流几乎没人再用 SSH。我当时用的方案是后端框架Spring Boot 2.7.x配合 Spring MVC持久层MyBatis配合 MyBatis Generator 生成基础 CRUD数据库MySQL 8.0前端Vue 2 Element UI基于若依框架的前端模板改造或者简单点用 Thymeleaf 做服务端渲染构建工具Maven权限控制Spring Security JWT为什么选 Spring Boot理由很实在它有自动配置起步依赖帮你把版本兼容的坑填了一大半内嵌 Tomcat本地开发直接 run main 方法就能启动配套的 Spring Security、Spring Data 生态成熟文档充足遇到问题搜得到答案。持久层在 MyBatis 和 JPA 之间我更推荐 MyBatis。原因有二第一毕设的增删改查如果全用 JPA 的自动方法答辩时“你写的 SQL 在哪里”这种问题会很难收场MyBatis 把 SQL 写在 Mapper XML 里老师一眼能看到你亲手写的 SQL话语权完全不一样。第二MyBatis 对复杂查询、动态 SQL 的支持更直观比如物资列表按条件筛选——物资名称模糊查询、分类筛选、上下架状态筛选——直接写动态 SQL 标签就行。如果你用若依这类脚手架会更省事它已经把 Spring Security、Redis、定时任务、代码生成器都集成了你只需要关注业务模块本身。但我的建议是不管用不用脚手架核心业务逻辑——库存扣减、积分流水——一定要自己完整写一遍不要全部靠代码生成器生成完事否则到答辩环节一深问就容易露馅。2.3 模块怎么切不走极端但也不能一锅烩模块划分直接决定开发效率和后期维护体验。有些人喜欢把功能全堆在几个 Controller 里结果一个 UserController 几百行后期想加功能到处找代码。这个项目我建议按业务域划分而不是按技术层划分系统管理模块用户管理、角色管理、菜单权限。物资管理模块物资分类、物资信息、入库管理、出库管理、库存管理。积分管理模块积分发放、积分扣减、积分流水查询。兑换管理模块用户提交兑换申请、管理人员审核、核销出库。统计报表模块物资发放统计、积分核销统计、月度报表、图表展示。平台门户模块首页信息展示、公告发布、用户积分与物资浏览。按业务域切分的好处是每个模块对应一个包包内再按 controller、service、mapper、entity 分层。这样既符合开发规范又能在答辩时说清楚模块边界不至于被问到“这个删除逻辑放哪个方法里”时当场卡壳。同时我建议不要在项目里设置无意义的过度抽象层。毕设项目不是企业级框架表演抽象层次越多阅读成本越高。简单直接一个 Service 处理一个业务域的核心逻辑用 Transactional 管理事务必要的地方加锁或使用乐观锁完全够用。3. 数据库设计一张表一张表过3.1 核心表的字段与关系数据库是整个系统能跑起来的地基也是答辩时最容易被追问的部分。我的设计里有九张核心表挑重点说sys_user用户表字段id、username、passwordBCrypt 加密存储、real_name、phone、user_type管理员/运营人员/志愿者/群众、points_balance积分余额、status、create_time。说明user_type 字段用字典值存储对应不同角色积分余额直接冗余在用户表上但必须配合积分流水表保证一致性后面会详细讲。sys_role 与 sys_user_role角色与用户关联表虽然业务上可以按 user_type 区分角色但还是建议把角色单独建模因为不同用户可能会有多种身份答辩时也更规范。goods_category物资分类表id、name、sort_order。goods物资表id、category_id、name、unit单位kg/袋/桶、specification规格5kg/10kg、stock_quantity当前库存、point_price所需积分、status0下架 1上架、create_time。注意当前库存是冗余字段真正的权威数据在库存流水表中每次出入库都写流水并更新快照。stock_record库存流水表id、goods_id、change_type1入库 2出库 3盘点调整、change_quantity正负值、before_quantity、after_quantity、operator_id、remark、create_time。这张表是整个物资模块的核心任何库存变化都要在这里留下记录。points_record积分流水表id、user_id、change_type1发放 2兑换扣减 3退回、change_points正负值、before_points、after_points、order_id关联兑换单可为空、remark、create_time。exchange_order兑换单表id、order_no、user_id、total_points、status0待审核 1已通过待领取 2已领取 3已驳回、reviewer_id、review_time、create_time。exchange_order_item兑换单明细表id、order_id、goods_id、goods_name、point_price、quantity、subtotal。donation_record捐赠记录表id、donor_name、donor_phone、goods_name、goods_quantity、donation_time、accept_admin_id、remark。这个表结构的主线逻辑是所有余额和库存都是“冗余快照”真正的信任来源是流水表。每次发积分、扣积分、入库、出库都必须写流水然后更新快照字段这两步必须放在同一个数据库事务里。3.2 积分流水设计别把余额当存储直接改积分是受助群众在平台里的“资产”它的正确设计是流水式而非余额式。用户页面显示“当前积分200”这个 200 只是给用户看的快照真正判断操作是否合法要看积分流水的合计。否则工作人员手滑直接 UPDATE 用户表把余额改成 999这条数据的来源就完全无法追溯了。积分发放和扣减的推荐做法是用一条积分流水分录记录每一次变动的前后值。发放逻辑做成一个“加积分”的 Service 方法校验发放对象、查当前余额、计算新余额、插入流水、更新余额全部包在一个事务里。同理兑换扣减积分时要校验余额是否充足扣减成功后生成兑换单明细与积分流水。如果兑换单被驳回则新增一条积分退回流水。这样整个积分的生命周期就有了完整审计链。这里有一个细节很多人会忽略points_record表里的 before_points 和 after_points 字段一定要记录不要嫌冗余。这两个字段将来在做对账、排查数据异常时就是救命稻草答辩时也是可以主动展示的设计亮点。3.3 物资状态机从入库到领用的状态流转物资不是“进了系统就是上架”这么简单。我的项目里定义了完整的状态流转草稿待入库工作人员登记物资信息但尚未实际到货。已入库在库可用物资到货验收入库库存增加。已上架可兑换物资审核通过用户端可见产生积分兑换入口。已下架暂停兑换库存不足、商品过期或需盘点时下架用户端不可见但库存仍在。已出库核销/领用用户提交兑换单运营人员核销出库。重点解释一下“兑换申请核销”的逻辑用户提交兑换申请后不直接扣库存和积分而是生成兑换单状态为待审核。运营人员在后台审核时判断库存是否充足、积分是否足够。审核通过后库存和积分才真正扣减兑换单状态变为“待领取”用户到店领取时运营人员点击“确认领取”兑换单状态变为“已领取”驳回时积分退回。这个设计解决了核心一致性问题防止“用户在页面上看到库存数量提交申请却发现库存已经没了”这种尴尬。当然也有人用“下单即锁定库存”的模式但对公益系统来说待审核更贴合实际运营节奏因为运营人员就是“人肉库存数据库”人工审核本就是场景特色。状态机的每一步变化都要在代码里用枚举或常量定义不要让状态字段散落在一堆魔法数字里。后面写 SQL 条件筛选时你会非常感谢这个设计。4. 核心功能实现不是所有代码都值得写4.1 登录与角色权限三分钟搞定没那么简单很多同学把登录功能理解为“用一张表存用户名密码登录时查一下Session 存个用户 ID页面显示欢迎 XXX”。这确实是最基础的做法但放到毕设里“设计感”不够。我这里用的是 Spring Security JWT 的方案支持前后端分离。前端登录成功后拿到 token后续请求在请求头携带Authorization: Bearer xxx后端通过自定义过滤器解析 token 并塞入 SecurityContext。这样做的好处一是无状态不依赖 Session分布式部署时不用考虑 session 共享问题老师问起来你能说得清楚二是权限控制可以在方法级别用 PreAuthorize 注解实现比如“只有运营人员能调用积分发放接口”。角色权限这块我强烈建议做一个简单的权限矩阵维护“角色-菜单-操作”的对应关系。界面上用 Vue Router 的前置路由守卫做按钮级控制后端每个写操作接口上都必须加权限校验绝对不能只靠前端隐藏按钮来“防盗”。我在项目里吃过亏第一次做完只做了前端隐藏后台管理接口直接被人拿 Postman 一路调到底库存被改乱了之后才意识到权限必须从后端做。还有一个容易被忽略的点密码存储。如果明文存密码老师翻一下数据库就能挑出毛病。用 BCrypt 加密存储格式像$2a$10$...登录时用 matches 方法比对。加盐逻辑内置不用自己造轮子两行代码的事。4.2 物资入库与领用库存扣减的正确姿势物资模块最容易出问题的点是库存扣减时出现负数或者数据不一致。推荐做法入库和出库都走统一的 Service 方法所有操作必须包裹在事务里。以“发放物资出库”为例伪代码如下Transactional(rollbackFor Exception.class) public void exchangeDelivery(Long orderId, Long adminId) { // 1. 查询兑换单校验状态必须是已通过待领取 ExchangeOrder order exchangeOrderMapper.selectById(orderId); if (order null || !ExchangeStatus.PENDING.equals(order.getStatus())) { throw new BusinessException(兑换单不存在或状态错误); } // 2. 查询兑换单明细逐个扣减库存必须加行锁 ListExchangeOrderItem items exchangeOrderItemMapper.selectByOrderId(orderId); for (ExchangeOrderItem item : items) { Goods goods goodsMapper.selectByIdForUpdate(item.getGoodsId()); if (goods.getStockQuantity() item.getQuantity()) { throw new BusinessException(【 goods.getName() 】库存不足); } // 3. 扣减库存 goodsMapper.decreaseStock(goods.getId(), item.getQuantity()); // 4. 写库存流水 stockRecordMapper.insert(StockRecord.build(goods.getId(), -item.getQuantity(), adminId, 兑换出库)); } // 5. 更新兑换单状态为已领取 exchangeOrderMapper.updateStatus(order.getId(), ExchangeStatus.FINISHED.getCode(), adminId); }关键点有两个。第一个是selectByIdForUpdate这一步用SELECT ... FOR UPDATE给物资记录上了行锁防止两个管理员同时核销同一商品时出现脏读。第二个是“先校验、后操作、再写流水”的顺序不能乱校验不通过直接抛异常事务回滚保证不会出现扣了库存但没写流水的情况。4.3 积分兑换的并发控制别让两个人同时花掉同一积分积分并发问题比库存还要隐蔽。我压测的时候复现过一个经典问题用户 A 只剩 50 积分同时提交了两次兑换 50 积分的申请第一单通过了第二单如果后端只是“先查积分余额再扣减”两次查询都拿到 50 分就会导致积分透支。解决方案我用的是乐观锁版本号。即在用户表上维护一个 version 字段每次更新积分余额时带上版本条件UPDATE sys_user SET points_balance points_balance - #{cost}, version version 1 WHERE id #{userId} AND version #{oldVersion}如果 update 影响行数为 0说明版本不一致有并发操作直接提示“您的积分正在变动中请刷新后重试”。一个 UPDATE 语句就解决了问题不需要 Redis 分布式锁毕设阶段数据库层面的乐观锁完全够用。兑换单审核时同理管理员点击“通过”的那一刻要重新到明细里校验每件商品的库存并锁定而不是直接用审核前查询到的库存快照做判断。上面代码里已经用selectByIdForUpdate处理了这件事这里再强调一次。4.4 数据统计与可视化让答辩多点亮点统计报表是爱心超市系统的“门面担当”。物资发放了多少、哪类物资消耗得最快、积分核销率怎么样、月度趋势如何这些数据做出来往 PPT 上一放能把项目的信息化水平拉上一个档次。我实现的时候用了一个 Dashboard 页面包含几个核心图表物资发放总量趋势近 6 个月折线图物资类别发放占比饼图积分发放/核销月度对比柱状图兑换单状态分布漏斗图后端统一写一个 StatisticsController用 group by 聚合查询返回 VO前端直接用 ECharts 绑定。SQL 示例SELECT DATE_FORMAT(create_time, %Y-%m) AS month, SUM(change_points) AS issue_points FROM points_record WHERE change_type 1 GROUP BY month ORDER BY month;实测发现如果表没做索引这类 group by 聚合在数据量到 5 万行时会有明显掉速。所以建表时记得给 create_time 和 change_type 加联合索引。这种细节你自己不写根本发现不了写了就是踩过坑的经验答辩时可以主动提。5. 实操过程从环境搭建到跑通第一个接口5.1 环境准备JDK、MySQL、IDEA、Maven 一步到位我用的版本组合是 JDK 8 Maven 3.6 MySQL 8.0 IDEA 2023.1。注意 JDK 8 和 MySQL 8.0 的时区问题数据库连接 URL 一定要加上serverTimezoneAsia/Shanghai否则启动时会报服务器时区无法识别的错误。这个坑几乎每个第一次连 MySQL 8 的人都会踩一遍。MySQL 建议本地安装即可不用折腾容器化。安装时注意字符集选 utf8mb4不然存生僻字或特殊符号会报错。建库建用户后权限只给当前库不要用 root 到处跑。IDEA 里几个配置Maven 用阿里云镜像加速依赖下载设置 JDK 版本为 1.8打开注解处理器lombok 需要。这一步如果不配后面写实体类用 Data 注解时会直接编译报错。5.2 从零搭起 Spring Boot 项目骨架最快的方式是去 start.spring.io 生成骨架勾选 Spring Web、MySQL Driver、Lombok然后引入mybatis-spring-boot-starter、validation、jwt等依赖。项目结构我建议如下com.example.ahss ├── config // 配置类MyBatis驼峰映射、跨域、JWT拦截器、安全配置 ├── controller // 接收请求、参数校验、返回统一响应体 ├── service // 业务逻辑层事务和锁都在这 ├── mapper // MyBatis Mapper接口 ├── entity // 实体类 ├── dto // 请求/响应对象避免直接把实体暴露给前端 ├── vo // 视图对象比如统计图表数据 ├── common // 工具类、常量、统一异常、统一返回类 ├── enums // 状态枚举 └── AimsApplication.java统一响应体我写了一个ResultT { code, message, data }类所有 Controller 都返回这个结构。这是一件小事但极大提升了前后端联调效率也显得项目规范。全局异常处理器也要写用 RestControllerAdvice 捕获业务异常和系统异常不要让堆栈信息裸奔给前端。5.3 前端页面怎么处理不精前端也能拿得出手前端这块我不建议从零手搓。我的做法是使用 Vue 2 Element UI 若依框架的前端模板改造。若依自带表格、表单、分页、弹窗组件改起来很快你只需要专注业务页面。具体说几个核心页面实现物资列表页用 el-table 展示物资信息顶部筛选框按分类、名称、状态筛选分页参数传给后端。兑换单审核页el-table 带多选复选框点审核弹出抽屉显示明细通过/驳回按钮回调后端接口。用户中心页积分余额展示、待领取的兑换单 tab、可兑换物资卡片墙。前后端跨域问题在后端配一个 CorsConfiguration Bean把允许的来源、方法、头都放开。本机开发联调阶段不用太纠结能跑通就行。6. 常见问题与排查技巧实录6.1 数据库连接超时的排查现象项目启动后第一次请求耗时几秒但过了一段时间再发请求直接报MySQL Connection is not available或Communications link failure。原因MySQL 默认 wait_timeout 是 8 小时。连接空闲久了被服务端断开而连接池里的旧连接不知道还在傻等。解决方式有两个一是连接 URL 上加autoReconnecttrue二是在 HikariCP 配置里设置maxLifetime略小于 MySQL 的 wait_timeout并设置idleTimeout。我项目里设的 maxLifetime 是 560000ms等一段时间自动剔除失效连接问题就消失了。6.2 MyBatis 动态 SQL 的坑动态 SQL 的坑主要出在if test...的参数判断上。搜索条件是 String 类型的 name判断为空要用name ! null and name ! 参数是 Integer 类型的 status判断就写成status ! null。有些人乱用 OGNL 表达式比如status ! 结果 0 被当成了空字符串查询条件直接被丢弃数据全量返回。另一个常见坑是foreach标签在 in 查询里循环明细项如果集合为空会生成IN ()语法错误。解决办法是在外层套if testlist ! null and list.size() 0。6.3 积分并发扣减的压测与修复4.3 节已经说了乐观锁的解决方式这里补充一下压测验证方法。我用 JMeter 开 20 个线程并发提交同一用户的两次兑换申请。修复前日志里能看到两次扣减都成功用户积分出现负数修复后第二次 update 影响行数为 0被乐观锁拦截抛出提示。这个过程我录了屏压在答辩 PPT 里老师一看就能理解项目深度。7. 答辩准备与项目扩展方向7.1 答辩时老师最常问的问题结合我旁听多场答辩和做评委的经验老师面对这种系统类项目翻来覆去就是这几个问题提前准备好答案就好为什么不用 JPA 要用 MyBatis答复杂查询和动态 SQL 写起来直观答辩时能展示 SQL 能力JPA 自动方法多做简单 CRUD。积分余额和积分流水怎么保证一致答事务 流水表先查流水合计再更新余额版本号乐观锁控制并发。库存扣减遇到并发怎么办答行锁SELECT FOR UPDATE 库存流水事务包裹全部操作。系统部署方案是什么答后端 jar 包 前端打包静态文件 Nginx 反代 MySQL一台服务器就能跑。项目难点在哪里答并发扣减的一致性和多维统计查询的性能优化。不用背答案但要能说到点上。老师最反感的是“数据一致性就是加个事务吧”这种听起来像背书的回答。7.2 这个系统还能怎么扩展毕设答辩之后如果想继续做下去扩展空间不小增加小程序端让受助群众在手机上浏览物资、看积分、提交兑换后端接口完全复用。增加消息通知兑换单审核结果、库存补货提醒用 WebSocket 或短信推送。增加可视化大屏LED 大屏展示当日发放数量、积分消耗量、库存预警效果非常直观。对接条码/二维码入库扫码录入出库扫码核销把线下操作和信息系统的距离拉近。引入数据分析根据历史领用数据预测哪些物资更容易缺货提前补货。这些扩展方向选一个做都能让系统从“毕设级别项目”升级成“可以落地试点的公益信息化方案”。最后说点个人体会。这个项目最让我意外的不是技术本身而是这些看似常见的 CRUD 功能一旦放进真实业务场景里到处都藏着一致性、权限、状态的细节。当初我以为“爱心超市”就是个增强版进销存真正动手才发现积分流水、兑换审核、并发扣减这些设计每一项都能写出一篇故事。如果你也在做类似的毕设我的建议是不要急着写代码先把业务流程画明白把数据库表设计清楚后面自然行云流水。希望这篇总结能让你少踩几个我踩过的坑答辩顺利。