作为一个写过不少 JavaWeb 毕设项目的开发者,看到“星星行李寄存系统”这种题目,第一反应就是亲切。它属于那种典型的“业务边界清晰、CRUD 为主、管理端加用户端、技术栈可选空间大”的课设/毕设题,非常适合用来练手 SpringBoot+SSM 这套组合。而且这类项目在招聘季经常被拿来当面试项目讲,如果把底层逻辑吃透,一通百通,同类商城、预约、租赁系统都能套用。
我大概花了三个周末把整套星星行李寄存系统从零撸完,包括源码、数据库设计、前后端联调、部署上线,以及配套的论文文档和调试笔记。今天把这套东西拆开揉碎地讲一遍,重点讲清楚几个大部分人容易卡壳的地方:数据库怎么设计、计费逻辑怎么落地、订单状态怎么流转、以及拿到一套源码之后怎么最快跑起来。全文纯个人实战路线,不写“教科书废话”,能直接照着抄的那种。
1. 项目整体设计与思路拆解
1.1 行李寄存到底是个什么业务
星星行李寄存系统,本质上解决的是“线下行李寄存/物品保管”的线上化管理问题。传统场景里,火车站、商场、景区、酒店大堂都有行李寄存点,人工登记、手写小票、按时间人工计费。这套系统的目标就是把所有这些环节系统化,让用户能在线下单、让管理员能统一管柜子管订单。
很多同学会把“行李寄存”和快递柜混在一起,这是理解业务时很关键的误区。快递柜是无人的物联网设备,需要对接硬件;而这类毕业设计里的行李寄存系统,通常是软件层面的管理平台,核心角色只有三类:用户、前台/管理员、系统平台。用户提交寄存申请,管理员在线确认、分配柜子或仓位,系统记录时间并自动计算费用,用户取件时结算。
从这个角度说,它的业务模型和“停车场收费系统”“酒店房间预订系统”“自习室预约系统”高度相似。只要你把“行李订单”想成“停车记录”,把“柜子”想成“车位”,很多设计就能直接复用。这也意味着,你做完这一个项目,面试时完全可以把业务换成任意一个类似场景来讲,项目迁移成本极低。
1.2 角色与业务流程:先画清楚谁在用什么功能
做项目之前,第一件事不是写代码,而是把角色和流程理清楚。星星行李寄存系统的用户角色,我在设计时做了四个核心端:
- 普通用户端:注册、登录、在线下单寄存、查看我的订单、预约取件、在线支付/线下支付记录、查看寄存柜位置和收费标准。
- 管理员端:管理员登录、柜子管理(增删改查、柜子状态维护)、寄存订单管理(待确认、寄存中、已完成、已取消)、用户管理、计费规则配置、数据统计。
- 系统自动任务:超时提醒、欠费标记、柜子状态自动释放等(这部分可以用定时任务做,也可以简化为下单时主动判断)。
- 游客/未登录用户:只能浏览首页、收费标准、寄存须知,要寄存必须登录。
我建议所有人在动手写代码前,先画一张类似下面这种操作路径图(不用工具,手画也可以):
- 用户注册账号 -> 登录系统
- 用户发起寄存单:选择寄存开始时间、预估结束时间、行李件数、行李类型
- 系统根据行李类型和时段自动推荐柜型/仓位
- 用户提交订单,系统生成待确认订单
- 管理员在后台看到新订单,线下核实行李后点击“确认寄存”
- 系统生成取件码/取件凭证,订单状态变为“寄存中”
- 用户取件时,管理员确认后点击“完成订单”
- 系统按实际时长计算费用,用户支付,订单变为“已完成”
这套流程每一步都有明确的状态对应,后期做开发、写论文、画用例图、做测试用例都非常省事。很多同学一上来就建表写代码,结果做到一半业务逻辑含糊不清,反复改,这是我最想提醒的一点:业务流程永远在代码前面。
2. 技术选型解析:SpringBoot+SSM 组合的落地细节
2.1 为什么毕业设计、课设选这套技术栈最稳
星星行李寄存系统的题目里明确写了 Java + SpringBoot + SSM,我按实际开发情况来解释一下这个组合。SSM 是 Spring + SpringMVC + MyBatis 的简称,这是一套非常经典的 JavaWeb 企业级开发组合,而 SpringBoot 本质上是把 Spring 生态做了一次封装,让配置更少、启动更快、开发体验更好。
所以你会发现一个很有意思的点:SpringBoot 项目里照样可以用 SpringMVC 的注解(@Controller、@RequestMapping),照样可以用 MyBatis 的 Mapper 接口和 XML 映射文件。也就是说,SpringBoot + SSM 并不冲突,实际的项目形态通常是:SpringBoot 作为基础框架,SpringMVC 负责 Web 层,MyBatis 负责持久层,底层数据库用 MySQL,前端页面用 Thymeleaf 或 JSP + Ajax,也可以做个简单的前后端分离(Vue + 接口)。
选这套组合的理由很实在:
- 成熟稳定,学习资料多。你遇到任何一个报错,基本都能在搜索引擎找到解决方案,这是课设项目中最大的隐形优势。
- 生态完整。SpringBoot 整合 MyBatis 非常方便,只需要在 pom.xml 中加入对应的 starter,再配置数据源即可。
- 面试加分。Spring、SpringBoot、MyBatis 是 Java 后端面试绝对绕不开的三座大山,做这个项目的过程就是准备面试的过程。
- 工作量适中。相比微服务、分布式这类前沿但体量过大的架构,单体应用加上清晰分层,更适合在有限时间内完成并确保稳定运行。
2.2 版本选择与项目骨架搭建
版本问题是我每次都要强调的。很多同学一上来就装最新的 SpringBoot 3.x,结果发现依赖和配置和网上教程对不上,卡死在环境上。星星行李寄存系统这类经典毕设项目,我的推荐组合是:
- JDK:1.8(互联网教程最多、兼容性最好;如果你用高版本 JDK,请确保 Maven 编译器版本匹配)
- SpringBoot:2.7.x(2.x 系列的最后一个稳定大版本,网上绝大多数学术/项目教程都是基于 2.x)
- MyBatis:mybatis-spring-boot-starter 2.x 版本
- MySQL:5.7 或 8.0(注意驱动依赖不同,8.0 的驱动类名和 URL 参数略有差异)
- Maven:3.6+
- 前端:Thymeleaf 模板引擎 + Bootstrap + jQuery(简单、直观、不需要单独启动前端服务)
项目目录结构我建议这样分层:
com.star.luggage ├── controller // 控制层:接收请求、返回视图或JSON ├── service // 业务层:核心业务逻辑、事务控制 │ └── impl // 业务实现类 ├── mapper // MyBatis的Mapper接口 ├── entity // 实体类(对应数据库表) ├── dto // 数据传输对象(接收前端参数) ├── vo // 视图对象(返回前端的数据) ├── config // 配置类(跨域、拦截器、全局异常) ├── common // 通用工具(返回结果封装、常量、随机数工具) └── LuggageApplication.java // 启动类这套包结构可以说是我从大量商业项目里简化出来的“最小可靠结构”。controller 只做参数接收和结果转发,不写业务;service 负责事务和核心规则;mapper 只做数据库操作。后期无论是写单元测试、把项目改成微服务风格,还是出论文里的“系统架构图”和“模块设计图”,都能直接拿这套结构出来说。
3. 数据库设计:一张订单表如何撑起整个寄存业务
3.1 核心表结构与字段说明
这部分是星星行李寄存系统能不能做好、答辩时能不能讲深的关键。如果是第一次做项目,很常见的错误是只建一张“订单表”,恨不得不所有字段塞进去。但仔细观察业务,你会发现至少有四类核心数据:用户、柜子/仓位、订单、计费规则。它们之间关系很清晰:
- 用户表(user):一个用户可以有多个寄存订单,1 对 N。
- 柜子表(cabinet):一个柜子可以被多个订单先后使用,但在任一时刻只能被一个有效订单占用,所以设计上采用“状态字段 + 订单关联”的方式。
- 订单表(luggage_order):存每一条寄存记录,核心表。
- 计费规则表(fee_rule):配置不同柜型/行李类型的单价,方便管理员在后台修改,不用改代码。
下面是我实际使用的表结构,字段设计偏向实用,删减了过于冗余的部分:
用户表 user
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar(50) | 用户名,唯一 |
| password | varchar(100) | 密码,MD5或BCrypt加密存储 |
| phone | varchar(20) | 手机号 |
| real_name | varchar(50) | 真实姓名(寄存登记用) |
| id_card | varchar(18) | 身份证号(可选) |
| create_time | datetime | 注册时间 |
| status | tinyint | 状态:0 禁用,1 正常 |
柜子表 cabinet
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| cabinet_no | varchar(20) | 柜子编号,如 A-01 |
| cabinet_type | tinyint | 柜型:1 小号,2 中号,3 大号 |
| location | varchar(100) | 所在位置描述(如一楼A区) |
| status | tinyint | 状态:0 空闲,1 占用,2 维修 |
| current_order_id | bigint | 当前占用订单 ID(空闲时为 null) |
订单表 luggage_order
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| order_no | varchar(32) | 订单号,如 LG20240601001 |
| user_id | bigint | 下单用户 ID |
| cabinet_id | bigint | 分配的柜子 ID |
| luggage_type | varchar(20) | 行李类型:背包/行李箱/纸箱等 |
| luggage_count | int | 行李件数 |
| estimated_hours | decimal(10,2) | 预计寄存时长(小时) |
| start_time | datetime | 实际寄存开始时间 |
| end_time | datetime | 实际取件时间 |
| status | tinyint | 状态:0 待确认,1 寄存中,2 待支付,3 已完成,4 已取消 |
| total_amount | decimal(10,2) | 订单总金额 |
| pickup_code | varchar(8) | 取件码 |
| remark | varchar(255) | 备注 |
| create_time | datetime | 下单时间 |
实际做的时候,我在订单表里加了 pickup_code 这一列,这是非常关键的设计——用户取件时,前台只需要输入取件码就能快速锁定订单,不用像查快递一样翻手机号。取件码建议用 6 位随机数字或大写字母组合,避免生成过于规律的编号。
3.2 计费逻辑与状态流转:让规则落到数据库里
行李寄存系统最容易被问倒的点,不是 CRUD,而是“费用怎么算”。如果你只在 Java 代码里硬编码一个价格,那答辩时老师一定会问:“如果要在后台改价格怎么办?”正确的做法是建一张计费规则表,把每种柜型、每小时的单价,以及不足一小时的计费方式都配置化。
计费规则表 fee_rule 的字段大致如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| cabinet_type | tinyint | 柜型 |
| price_per_hour | decimal(10,2) | 每小时单价 |
| min_charge | decimal(10,2) | 最低消费(不足1小时按1小时或按最低收费) |
| over_time_price | decimal(10,2) | 超时单价(超过预计时长后的价格,可选) |
订单状态的流转,我建议用一个常量类来定义,避免代码里到处是魔法数字:
public class OrderStatus { public static final int PENDING_CONFIRM = 0; // 待确认 public static final int STORING = 1; // 寄存中 public static final int WAITING_PAY = 2; // 待支付 public static final int FINISHED = 3; // 已完成 public static final int CANCELED = 4; // 已取消 }状态流转规则:
- 用户下单 -> 状态为待确认(0)
- 管理员点击“确认寄存”,标记柜子为占用 -> 状态变为寄存中(1)
- 管理员点击“结算订单”,系统计算费用,状态变为待支付(2)
- 用户支付成功(或者管理员标记已收款)-> 状态变为已完成(3),柜子释放为空闲
- 用户超时未到店且未取消 -> 可手动取消或管理员强制取消(4)
我把状态流转想成“洗衣机的程序运行图”:每一步都有明确的前置状态和后置状态,严禁跨状态乱跳。比如状态为“寄存中”的订单,用户不能直接取消;状态为“已完成”的订单,不能被重复结算。这个逻辑在后端 Service 的判断里要用 if 条件逐层校验。
4. 核心功能实现:从下单、取件到后台管理
4.1 用户下单:一次事务里的“锁柜子+生成订单”
下单是整个系统最核心的链路,它涉及两个表的修改:占用一个柜子,同时插入一条订单记录。这两个操作必须放在同一个事务里,否则会出现“订单建了但柜子没锁住”或者“柜子锁了但订单没生成”的数据不一致问题。
下单 Service 的伪代码如下:
@Service public class LuggageOrderServiceImpl implements LuggageOrderService { @Resource private CabinetMapper cabinetMapper; @Resource private LuggageOrderMapper orderMapper; @Override @Transactional(rollbackFor = Exception.class) public Result createOrder(CreateOrderDTO dto, Long userId) { // 1. 查找一个空闲且符合柜型的柜子 Cabinet cabinet = cabinetMapper.findFreeCabinetByType(dto.getCabinetType()); if (cabinet == null) { return Result.error("当前没有空闲的柜子,请选择其他柜型或稍后再试"); } // 2. 生成订单号和取件码 String orderNo = generateOrderNo(); String pickupCode = generatePickupCode(); // 3. 构建订单对象 LuggageOrder order = new LuggageOrder(); order.setOrderNo(orderNo); order.setUserId(userId); order.setCabinetId(cabinet.getId()); order.setLuggageType(dto.getLuggageType()); order.setLuggageCount(dto.getLuggageCount()); order.setStatus(OrderStatus.PENDING_CONFIRM); order.setPickupCode(pickupCode); order.setCreateTime(new Date()); // 4. 锁定柜子 int lockResult = cabinetMapper.lockCabinet(cabinet.getId(), orderNo); if (lockResult == 0) { throw new RuntimeException("柜子已被其他人占用,请刷新后重试"); } // 5. 插入订单 order.setCabinetId(cabinet.getId()); orderMapper.insert(order); return Result.success(order); } }这里有两个容易踩的坑。
第一个是“高并发下多个用户同时选中同一个柜子”。如果先查询再更新,两个用户可能同时查询到同一个空闲柜子,然后竞争更新,导致重复占用。解决方案有两个层面:简单做法是用数据库的乐观锁,update cabinet set status = 1 where id = ? and status = 0,通过 update 返回影响行数来判断是否成功;更好一点的做法是引入 Redis 分布式锁或数据库悲观锁(select ... for update),不过毕设项目通常用乐观锁就足够了,我在代码里用的就是 update 影响行数判断。
第二个是“订单号生成规则”。不要用数据库自增 ID 直接当订单号展示给用户,容易暴露业务量,而且一旦遇到分布式扩展可能重复。我用的规则是:前缀 LG + 年月日 + 四位随机数,比如 LG202506120123。生成订单号时要做唯一性校验,虽然概率极低,但在循环里判断一下更稳。
4.2 取件结算:用分钟级时长与阶梯计费算出最终金额
取件是第二个核心链路。用户在任意时间到店取件,管理员在后台输入取件码或直接点开订单,点击“结算”,系统要根据实际寄存时长算出费用。
计算逻辑看起来简单,但如果不注意细节,会出现“多收了几十块钱”或者“少收了一笔超时费”的问题。我在实现时用了一个专门的方法:
public BigDecimal calculateAmount(LuggageOrder order, FeeRule feeRule) { // 实际寄存时长:从 startTime 到 endTime(秒/分钟/小时换算) long diffMs = order.getEndTime().getTime() - order.getStartTime().getTime(); double hours = diffMs / (1000.0 * 60 * 60); // 向上取整到小时,最少按1小时计算 long settleHours = (long) Math.ceil(hours); if (settleHours < 1) { settleHours = 1; } BigDecimal amount = feeRule.getPricePerHour() .multiply(BigDecimal.valueOf(settleHours)); // 如果设置过最低消费,取两者中的较大值 if (feeRule.getMinCharge() != null && amount.compareTo(feeRule.getMinCharge()) < 0) { amount = feeRule.getMinCharge(); } return amount; }这里容易犯的一个错误是使用 double 直接计算金额。在 Java 中,浮点运算会出现精度丢失,比如 0.1 加 0.2 不等于 0.3。所有涉及费用的字段务必使用 BigDecimal,而且单价和时长乘法要用 BigDecimal 的 multiply 方法。这是我从实际项目里总结出来的血泪教训,答辩时如果被问到“为什么用 BigDecimal”,这是一个高质量回答点。
还有一个业务细节:如果用户在下单时选择了“预计寄存时长”,结算时超过了怎么处理?我的方案是:超过预计时长后,系统把超出的时间单独用超时单价计算,并加收一定比例的服务费。这其实是商业项目里的常见激励用户守时的设计。如果没有这个需求,也可以简化为统一按时长计费,两种做法都可以在论文里写清楚。
4.3 后台管理:柜子状态刷新和数据统计
后台管理模块通常包含五大块:管理员登录、柜子管理、订单管理、用户管理、计费规则管理。其中柜子管理和订单管理的联动是要特别注意的。我在系统中给柜子加了一个 current_order_id 字段,这样管理员在柜子列表页可以直接看到一个柜子当前被哪个订单占用,点击订单号还能跳转到订单详情。这个体验在演示的时候会很加分,因为很多人的柜子和订单根本没有关联,演示起来断裂感很强。
数据统计部分可以做的很简约:统计今日新增订单、今日营收、总寄存订单数、柜子占用率。实现方式就是在 Mapper 里写对应的聚合 SQL,比如:
SELECT COUNT(*) AS totalOrders, SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS storingOrders FROM luggage_order WHERE create_time >= CURDATE();然后把结果封装成一个 dashboard VO 返回给前端,用 ECharts 画一个简约的折线图或者柱状图。这块不要过度设计,毕竟课设大部分是体外演示和答辩,不需要实时大屏那种复杂效果。但有一点建议:图表一定不要用截图或者写死数据,要真正接数据库查询,答辩时可以现场演示“新下一单 -> 图表数字变化”,这会给评审留下非常靠谱的印象。
5. 部署调试中高频问题与排查技巧
5.1 数据库连接报错:时区、驱动、字符集三连环
几乎每个第一次启动 SpringBoot + MyBatis 项目的人,都会在数据库连接这一步卡住。常见的报错是:
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized...这个报错的本质是 MySQL 8.0 默认使用 UTC 时区,而本地服务器是中国标准时间,JDBC 驱动无法识别。解决方案是在 application.yml 的数据库连接 URL 上加上参数:
spring: datasource: url: jdbc:mysql://localhost:3306/star_luggage?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver注意 MySQL 5.7 的驱动类是 com.mysql.jdbc.Driver,MySQL 8.0 的驱动类是 com.mysql.cj.jdbc.Driver。如果你用的 MySQL 8.0 但配了旧驱动,会提示找不到类或者加载失败。
此外,如果导入 SQL 文件后发现中文乱码,大概率是建库语句里没有指定 utf8mb4。建议建库时用:
CREATE DATABASE star_luggage DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;5.2 依赖冲突与启动失败:第一眼检查 pom.xml
SpringBoot 项目启动时最常见的报错之一是:
APPLICATION FAILED TO START Description: Field userMapper in com.star.luggage.service.impl.UserServiceImpl required a bean of type 'com.star.luggage.mapper.UserMapper' that could not be found.这个报错说人话就是:Spring 容器里没有创建 UserMapper 这个 Bean。原因不外乎三种:
- 启动类上没有加 @MapperScan 注解。解决办法是在启动类上加上 @MapperScan("com.star.luggage.mapper"),或者在每个 Mapper 接口上单独加 @Mapper 注解。
- Mapper 接口的 XML 文件没有被扫描到。检查 application.yml 中是否配置了 mybatis.mapper-locations: classpath:mapper/*.xml,并确保 XML 的 namespace 与接口全限定名一致。
- 包名扫描不到。启动类所在的包必须是所有子包的父包,否则 SpringBoot 默认的组件扫描会漏掉。
我的经验是:尽量统一在启动类加 @MapperScan,同时不要让 Mapper 接口和 XML 文件位置过于分散。XML 统一放在 resources/mapper 目录下面,命名和接口保持一致,比如 LuggageOrderMapper.java 对应 LuggageOrderMapper.xml。
5.3 前端接口 404、跨域和参数接收问题
如果前端使用 Ajax 或者分离式页面,最容易出现的问题有两个。
一个是跨域。浏览器默认不允许不同端口之间的请求互相访问,如果你用 Vue 的 devServer 跑前端(比如 8081 端口),后端跑在 8080 端口,直接访问会报 “Access-Control-Allow-Origin” 错误。解决方式是在后端写一个跨域配置类,或者在 Controller 上加 @CrossOrigin 注解。SpringBoot 2.7 中还可以用 WebMvcConfigurer 统一配置:
@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); } }另一个是参数接收不一致。前端传的是 JSON 对象,后端却用普通的 POJO 接收,而没有加 @RequestBody,就会导致所有参数都是 null。正确的做法是:POST 请求传 JSON 时,Controller 方法参数前要加 @RequestBody;如果是表单格式提交,则不需要加。这里的“为什么必须加”,在面试里也常会问到,实际上是 SpringMVC 的 HttpMessageConverter 在起作用,JSON 字符串需要 Jackson 反序列化才能变成 Java 对象。
5.4 调试时的三个实用技巧
除了以上问题,我在实际调通这套项目时还总结了三个技巧,它们帮你省下大量看起来“莫名其妙”的排查时间:
- 开启 MyBatis SQL 日志。在 application.yml 里配置 logging.level.com.star.luggage.mapper=debug,就能在控制台看到每一条执行的 SQL 和参数。这是排查“数据为什么没插进去”“查询结果为什么不对”最直接的方式。
- 善用全局异常处理器。写一个 @RestControllerAdvice 全局异常类,把业务异常、参数校验异常、未知异常分别返回成统一的 JSON 结构,前端就能友好提示错误原因,而不是抛出一大堆堆栈信息,我们后台调试也一眼能定位。
- 断电记忆式保存。前端开发时,尽量把列表页、详情页写成单一接口可刷新的模式,不要做过多的页面跳转,否则调试过程中一个参数传错,就要从头开始走一遍流程,非常浪费时间。
6. 从源码到论文:文档、调试说明与答辩经验
6.1 拿到一套源码后,怎么最快跑起来
不管这套星星行李寄存系统是别人分享给你的,还是你自己写的,按照下面的“四步法”可以避免 90% 以上的启动问题:
- 环境确认。先确认 JDK、Maven、MySQL 版本。如果你用的是 JDK17,而项目是基于 JDK8 编译的,大概率会在编译期报错(无法获取某个依赖或者语法不兼容)。最稳妥的方式是装一个 JDK8,并在 IDEA 的 Project Structure 里把 Project SDK 和 Module SDK 统一调成 1.8。
- 导入数据库。新建数据库,把项目里的 sql 文件导入。注意 sql 文件中的库名,如果你的数据库名不叫 star_luggage,需要修改 application.yml 中的 URL。
- 修改配置。重点检查三处:数据库用户名和密码、端口号(默认 8080,如果被占用改成 8081)、数据库 URL 时区参数。
- 启动并测试。启动主类后,访问 http://localhost:8080 看首页是否能打开,再走一遍“注册 -> 登录 -> 下单 -> 后台确认 -> 结算”全流程。
如果哪一步没走通,优先看启动日志中的第一行错误,而不是急着问别人。SpringBoot 的报错信息非常友好,80% 的问题都能在日志中找到明确关键字,比如“Port already in use”就是端口被占用,“Unknown database”就是数据库名不对。
6.2 论文与答辩:把项目讲成“有深度”的样子
很多同学项目代码写完了,却不知道论文怎么写。我这里分享一条通用的论文结构,也是我实际写星星行李寄存系统论文时用到的:
- 引言/绪论:写研究背景和意义(可以写旅游出行增多、行李寄存需求增长,但注意不要大段百度百科式的套话,要简洁)。
- 需求分析:描述系统角色、功能模块、用例图、用例说明。
- 系统设计:技术架构图、数据库 E-R 图、表结构设计、关键流程图。
- 系统实现:按模块写,每个模块给出界面截图和关键代码片段,配上简单说明。
- 系统测试:测试环境、测试用例表格、测试结果。
写论文和答辩时分寸很重要:不要在论文里堆大量代码,老师想看的是你对整个系统的理解,而不是代码复制。每张核心截图配一段“这个功能是怎么实现的”文字,把关键逻辑讲清楚。数据库设计部分多花笔墨,E-R 图和表字段说明是最能体现功底的。
答辩时老师们最常问的问题无非这几个:
- 你的系统用的什么架构?为什么选这个技术栈?
- 数据库几张表?表之间什么关系?哪里用了外键或逻辑关联?
- 你的计费功能是怎么实现的?如果并发量大,怎么保证不错乱?
- 你遇到的最大难点是什么?怎么解决的?
- 系统有没有安全设计?密码怎么存储的?
针对第 5 个问题,我想多说一句:密码一定不要明文存储,至少要用 MD5(加盐更好,直接用 BCrypt 也算中规中矩)。很多毕设系统的用户表密码字段都是明文,这在答辩时一旦被问到就会很被动。
我个人在实际操作中还有一个体会:不需要把系统做得大而全,但一定要让自己的核心模块做到“逻辑闭环”。比如星星行李寄存系统,哪怕其他页面都一般,只要下单、结算、报表这三条链路走得顺,演示时逻辑通顺,答辩老师的第一印象就会很不错。反而是那种功能塞了一大堆、点每个菜单都报错或者状态对不上的项目,一看就知道是拼凑出来的。最后再分享一个小技巧:如果你在给项目写说明文档,建议把所有接口整理成一张表,列出请求方式、请求地址、参数、返回值,调试的时候对着看,效率比翻代码快好几倍。这套方法在我后续做商城、做预约系统的时候也一直在用,非常受益。