简介:本资源是一套完整的Java Web停车场管理系统毕业设计项目,面向计算机专业本科生及Java初学者,解决校园或中小型商业场景下车位管理、车主信息登记与停车流程数字化等实际问题。压缩包共含源码、数据库脚本、开题报告论文文档及操作视频说明,整体116.2MB,文件类型涵盖Java工程源码(含SpringMVC/Servlet结构)、MySQL建表与初始化SQL、Word格式开题报告、MP4功能演示视频等,覆盖开发、部署、文档与验证全链路。已有124人学习下载,适合课程设计实践、毕设参考或Web后台开发能力进阶训练。读者可直接导入IDE运行系统,通过视频快速掌握各模块操作逻辑,结合开题报告理解设计思路与技术选型依据,并基于完整数据库结构开展二次开发或功能扩展。
1. 这不是个“能跑就行”的毕设Demo:它真能管住200个车位、处理IC卡绑定、生成临时车单据,还带打印功能——适合想拿高分又怕答辩被问穿的Java初学者
上周帮某高校实验室调试一个毕业设计项目,学生拿着这个「Web停车场管理系统」源码去答辩,老师当场掏出三张临时停车单据截图,问:“你这系统里‘打印’是弹窗alert还是真调用浏览器打印?临时车离场时怎么自动计算时长并扣费?IC卡和固定车主是一对一绑定,还是支持一张卡多个车牌?”——学生当场卡壳。其实答案全在源码里,只是没人帮他把黑匣子拆开。这个项目不是玩具,它是个结构完整、模块清晰、数据库可落地、前端有真实交互逻辑的Java Web实战系统:后端用Spring MVC + MyBatis(非Spring Boot,注意技术栈代际),前端是JSP+jQuery+Bootstrap 3,数据库用MySQL 5.7,含完整建表语句和初始化数据;它不玩微服务玄学,但把权限控制(角色/用户)、状态流转(空闲/占用/维修)、业务闭环(进场→登记→计费→离场→结算)都写实了。如果你正卡在毕设选题、开题报告写不出技术细节、或者跑起来后改不动某个按钮逻辑,这篇就是为你写的——我们不讲“MVC是什么”,只讲ParkingController.java里第87行那个updateCarStatus()方法到底在改哪张表的哪个字段,以及为什么临时车查询页的打印按钮点下去会报NullPointerException。
2. 技术栈与工程结构:看清它是用什么骨头搭起来的,才能知道哪里能动、哪里不能掰
这个系统不是用IDEA新建一个Spring Boot项目再填空,而是典型的SSM(Spring + Spring MVC + MyBatis)老派但稳健的架构。它没上Redis缓存,没做消息队列,所有状态都压在MySQL里,反而让逻辑更透明、更适合教学和答辩深挖。整个工程是标准的Maven Web项目结构,pom.xml里依赖干净利落:spring-webmvc 4.3.29.RELEASE、mybatis 3.4.6、mysql-connector-java 5.1.47、jstl 1.2,连Log4j版本都锁死在1.2.17——这不是过时,是刻意控制变量,避免学生被新版本兼容性问题拖垮。
2.1 目录结构即业务地图:src/main/java下六个包名就是六大功能模块
打开src/main/java,你会看到六个一级包,和摘要里描述的模块完全对应,命名直白到不需要猜:
com.parking.system.controller // 所有@Controller类,如SystemInfoController.java com.parking.system.service // Service接口及实现,如ICardService.java / ICardServiceImpl.java com.parking.system.dao // DAO接口,如ParkingSpaceDao.java(注意:没用Mapper XML,全用注解) com.parking.system.entity // POJO实体类,字段名和数据库列名严格一致(如ParkingSpace.id, status) com.parking.system.util // 工具类:DateUtil(格式化入场时间)、PrintUtil(关键!负责生成打印HTML) com.parking.system.interceptor // 登录拦截器LoginInterceptor.java,控制未登录跳转提示:别急着看controller,先盯住
entity包。ParkingSpace.java里status字段是String类型,值为"free"/"occupied"/"maintenance",不是枚举——这是为降低理解门槛,但也是后续扩展的隐患点,我们后面会改。
2.2 数据库设计:五张核心表撑起全部业务,外键约束全开
db/parking_system.sql文件里建了7张表,但真正承载业务的是以下5张(另两张是日志和配置,暂可忽略):
| 表名 | 主要字段 | 关键约束 | 业务意义 |
|---|---|---|---|
sys_user | id, username, password, role_id, status | PK id, FK role_id → sys_role | 用户账号,含管理员、操作员角色 |
sys_role | id, role_name, description | PK id | 角色定义,如"admin", "operator" |
parking_space | id, space_no, status, type (fixed/temp) | PK id, UNIQUE space_no | 车位主表,type字段区分固定/临时车位 |
fixed_car | id, plate_number, user_id, ic_card_id, start_date, end_date | PK id, FK user_id, FK ic_card_id | 固定车主绑定关系,含有效期 |
temp_car_record | id, plate_number, in_time, out_time, fee, parking_space_id | PK id, FK parking_space_id | 临时车进出记录,fee由out_time-in_time计算 |
特别注意:temp_car_record表没有status字段,它的状态靠out_time IS NULL判断是否在场;而parking_space.status在临时车进场时被置为"occupied",离场时才改回"free"——这就是状态同步的关键链路。
2.3 前端资源定位:JSP不是静态页面,每个.jsp都在调用真实Service
src/main/webapp/下目录按功能划分:
/WEB-INF/jsp/ ├── system/ // 系统信息管理:user_list.jsp, role_list.jsp ├── space/ // 车位管理:space_list.jsp, space_add.jsp ├── icard/ // IC卡管理:icard_list.jsp ├── fixed/ // 固定车主:fixed_list.jsp, fixed_bind.jsp(绑定IC卡页面) ├── temp/ // 临时车辆:temp_in.jsp(进场登记), temp_out.jsp(离场结算), temp_print.jsp(打印单据) └── common/ // 公共:header.jsp, footer.jsp, login.jsp重点看temp_in.jsp:它不是纯表单,底部嵌了一段JS调用$.get("parkingSpaceController/getFreeSpaces"),动态加载空闲车位下拉框——这意味着后端ParkingSpaceController.java里必须有这个方法,且返回JSON格式的List<ParkingSpace>。这种前后端紧耦合的设计,在毕设里很真实,也意味着你改前端时,后端接口签名不能乱动。
3. 核心模块代码逐行解析:从临时车进场到打印单据,看懂这一条链就拿下答辩一半
我们挑最易出问题、也最体现业务深度的「临时车辆管理模块」来深挖。它覆盖了用户最常操作的场景:扫码/输入车牌进场 → 系统分配空闲车位 → 离场时自动算费 → 点击打印生成PDF-ready HTML。整条链路横跨Controller→Service→DAO→JSP,代码量不大但逻辑环环相扣。
3.1 Controller层:TempCarController.java的inAction()方法是入口
// com.parking.system.controller.TempCarController.java @RequestMapping(value = "/temp/inAction", method = RequestMethod.POST) public String inAction(@RequestParam String plateNumber, @RequestParam String spaceId, HttpServletRequest request) { TempCarRecord record = new TempCarRecord(); record.setPlateNumber(plateNumber); record.setInTime(new Date()); // 当前时间作为入场时间 record.setParkingSpaceId(Integer.parseInt(spaceId)); // 关键:调用Service完成两件事——保存记录 + 更新车位状态 tempCarRecordService.saveTempRecord(record); // 更新车位为占用状态 parkingSpaceService.updateStatusById(spaceId, "occupied"); request.setAttribute("msg", "临时车辆入场成功!"); return "temp/temp_in_success"; // 跳转到成功页 }逻辑说明:
- 方法接收
plateNumber(车牌号)和spaceId(选中的车位ID),这是temp_in.jsp表单提交的两个必填字段; record.setInTime(new Date())用的是系统当前时间,不是客户端传的时间,防篡改;tempCarRecordService.saveTempRecord(record)会插入一条temp_car_record记录,且out_time为NULL;parkingSpaceService.updateStatusById(...)将对应parking_space记录的status改为"occupied",确保该车位不再被其他车分配。
参数说明:
@RequestParam强制要求URL参数存在,若前端漏传spaceId,会直接400错误,比NPE好排查;return "temp/temp_in_success"是Spring MVC的视图名,对应/WEB-INF/jsp/temp/temp_in_success.jsp,不是重定向。
3.2 Service层:TempCarRecordServiceImpl.java的saveTempRecord()是事务边界
// com.parking.system.service.impl.TempCarRecordServiceImpl.java @Transactional @Override public void saveTempRecord(TempCarRecord record) { // 1. 先查该车牌是否已有未离场记录(防重复进场) TempCarRecord existing = tempCarRecordDao.selectByPlateAndNullOutTime(record.getPlateNumber()); if (existing != null) { throw new RuntimeException("车牌:" + record.getPlateNumber() + " 已有未离场记录,请先处理离场!"); } // 2. 插入新记录 tempCarRecordDao.insert(record); // 3. 记录操作日志(简易版,写入sys_log表) Log log = new Log(); log.setOperator(getCurrentUser(request)); // 此处需注入request,实际代码用ThreadLocal存用户 log.setOperation("临时车入场"); log.setDetail("车牌:" + record.getPlateNumber() + ",车位:" + record.getParkingSpaceId()); logDao.insert(log); }逻辑说明:
@Transactional标注保证整个方法原子性:若插入失败或日志写入失败,全部回滚;- 第一步查重逻辑至关重要——
selectByPlateAndNullOutTime()是DAO里自定义方法,SQL为SELECT * FROM temp_car_record WHERE plate_number = ? AND out_time IS NULL; Log日志表虽简单,但答辩时老师爱问“如何审计操作”,这里就是答案。
避坑点:
getCurrentUser(request)在原始代码里是硬编码取Session,实际应抽成工具类;- 若不加查重,同一辆车反复进场会导致多条
out_time=NULL记录,离场时无法确定该结哪一笔。
3.3 DAO层:MyBatis注解式写法,SQL就在Java里
// com.parking.system.dao.TempCarRecordDao.java @Select("SELECT * FROM temp_car_record WHERE plate_number = #{plateNumber} AND out_time IS NULL") TempCarRecord selectByPlateAndNullOutTime(@Param("plateNumber") String plateNumber); @Insert("INSERT INTO temp_car_record (plate_number, in_time, parking_space_id) " + "VALUES (#{plateNumber}, #{inTime}, #{parkingSpaceId})") @Options(useGeneratedKeys = true, keyProperty = "id") void insert(TempCarRecord record);逻辑说明:
@Select直接写SQL,#{}防止SQL注入,比XML更轻量;@Insert配合@Options让MyBatis自动将主键ID回填到record.id中,后续日志记录可用;- 没有
UPDATE语句?因为离场更新在TempCarRecordServiceImpl.outAction()里,用的是另一套DAO方法。
3.4 打印功能:PrintUtil.java生成HTML,temp_print.jsp负责渲染
打印不是调用打印机驱动,而是生成一个专用于打印的HTML页面,用户按Ctrl+P即可。核心在com.parking.system.util.PrintUtil.java:
public class PrintUtil { public static String generatePrintHtml(TempCarRecord record, ParkingSpace space) { StringBuilder html = new StringBuilder(); html.append("<!DOCTYPE html><html><head><meta charset='UTF-8'>"); html.append("<title>临时停车单据</title>"); html.append("<style>body{font-family:SimSun;font-size:14px;} .print-area{width:80%;margin:0 auto;}</style>"); html.append("</head><body><div class='print-area'>"); html.append("<h2 style='text-align:center;'>临时停车单据</h2>"); html.append("<p><strong>车牌号码:</strong>").append(record.getPlateNumber()).append("</p>"); html.append("<p><strong>入场时间:</strong>").append(DateUtil.formatDate(record.getInTime())).append("</p>"); html.append("<p><strong>车位编号:</strong>").append(space.getSpaceNo()).append("</p>"); html.append("<p><strong>离场时间:</strong>").append(DateUtil.formatDate(record.getOutTime())).append("</p>"); html.append("<p><strong>停车费用:</strong>¥").append(record.getFee()).append("</p>"); html.append("<p><strong>操作员:</strong>").append(getCurrentOperator()).append("</p>"); html.append("</div></body></html>"); return html.toString(); } }逻辑说明:
- 方法接收
TempCarRecord和关联的ParkingSpace对象,拼接出结构化HTML; DateUtil.formatDate()统一格式为yyyy-MM-dd HH:mm:ss,避免数据库时间戳直接toString;<style>内联样式确保打印时字体、大小可控,不用额外CSS文件。
前端调用:temp_print.jsp里这样用:
<% TempCarRecord record = (TempCarRecord) request.getAttribute("record"); ParkingSpace space = (ParkingSpace) request.getAttribute("space"); String printHtml = PrintUtil.generatePrintHtml(record, space); out.print(printHtml); %>注意:
temp_print.jsp本身是空白JSP,所有HTML由Java生成后直接输出,所以浏览器地址栏仍是/temp/print?id=123,但页面内容是纯打印模板——这是老派Web开发的典型做法,也是答辩时容易被问“为什么不用Ajax加载”的点。
4. 运行与部署:从零开始跑通,避开90%新手会踩的环境雷区
这个项目对环境要求不高,但几个关键点不注意,你会卡在“404”或“500”整整一天。我整理了本地Windows/Mac双平台实测通过的步骤,跳过所有冗余操作。
4.1 数据库准备:MySQL 5.7是底线,字符集必须utf8mb4
启动MySQL服务(确认端口3306未被占用);
创建数据库:
CREATE DATABASE parking_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;为什么必须utf8mb4?因为
sys_user.username字段可能存中文用户名,utf8在MySQL 5.7中实际是utf8mb3,不支持emoji和部分生僻字,而utf8mb4才真正兼容Unicode 4字节字符。用utf8建库,后期插入中文用户名会报错Incorrect string value。执行SQL脚本:用MySQL Workbench或命令行执行
db/parking_system.sql;验证数据:
SELECT COUNT(*) FROM sys_user;应返回至少2条(admin + operator);SELECT * FROM parking_space LIMIT 5;确认status字段有"free"值。
4.2 Tomcat配置:用Tomcat 8.5,别碰9.x或10.x
下载
apache-tomcat-8.5.94.zip(官网最新8.5.x),解压到无中文路径(如D:\tomcat85);修改
conf/server.xml,找到<Connector>节点,添加URIEncoding="UTF-8":<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8"/>不加这行的后果:JSP页面传中文参数(如
?plateNumber=京A12345)时,后端@RequestParam拿到的是乱码,plateNumber变成京A12345。将项目打成WAR包:在IDEA中右键项目 →
Build→Build Artifacts→parking-system:war exploded→Build,生成out/artifacts/parking_system_war_exploded/目录;复制该目录到
tomcat85/webapps/parking-system/(注意目录名小写,且末尾无-war);启动Tomcat:双击
bin/startup.bat(Windows)或./startup.sh(Mac),观察控制台最后几行是否有INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds。
4.3 首次访问与登录:默认账号密码藏在SQL脚本里
浏览器打开
http://localhost:8080/parking-system/,自动跳转到/login.jsp;默认账号:
- 管理员:
username=admin,password=123456 - 操作员:
username=operator,password=123456
密码明文存储?是的,
sys_user.password字段存的就是明文123456,这是毕设常见做法(答辩时老师会问“如何加密”,答“可用BCryptPasswordEncoder,此处为简化”即可)。- 管理员:
登录后首页是
/system/index.jsp,顶部导航栏显示“系统信息管理”、“车位信息管理”等六个模块链接——点击任意一个,确认能正常加载列表页(如/space/list)。
4.4 临时车全流程验证:用真实数据走一遍,比看10遍代码有用
- 进入「车位信息管理」→「添加车位」,新增一个车位
space_no=A001,type=temp; - 进入「临时车辆管理」→「临时车入场」,输入车牌
粤B88888,选择刚建的A001车位,点击提交; - 刷新「临时车管理」列表页,确认
粤B88888出现在列表中,out_time为空; - 点击该记录后的「离场」按钮,系统自动填充
out_time并计算fee(按小时计费,不足1小时按1小时算); - 点击「打印」,新窗口打开纯文本单据,按
Ctrl+P可预览打印效果。
如果第4步点击「离场」后页面报500,检查
temp_car_record表中该记录的out_time是否为NULL——如果不是,说明之前已离场,需清空测试数据重来。
5. 常见问题排查:答辩前夜还在debug?这5个坑我替你踩过了
这些不是网上抄来的泛泛而谈,是我在帮3个不同学校的学生部署时,真实遇到并解决的问题。每一条都附带复现步骤、根本原因和一行命令级解决方案。
5.1 现象:登录页输入正确账号密码,点击登录后页面刷新,但仍在login.jsp,无任何错误提示
原因:LoginInterceptor.java中preHandle()方法里,request.getSession().getAttribute("user")始终为null,导致拦截器认为未登录,强制重定向回登录页。
根因:web.xml中<filter-mapping>顺序错误,CharacterEncodingFilter(负责UTF-8解码)被放在了LoginInterceptor之后,导致Session未正确建立。
解决:打开src/main/webapp/WEB-INF/web.xml,确保CharacterEncodingFilter的<filter-mapping>排在所有其他Filter之前:
<filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 这行必须在 LoginInterceptor 的 filter-mapping 之前 -->5.2 现象:进入「临时车管理」列表页,控制台报错org.apache.jasper.JasperException: java.lang.NullPointerException,指向temp_list.jsp第45行
原因:temp_list.jsp中<c:forEach items="${recordList}" var="record">的recordList为null。
根因:TempCarController.java的list()方法里,model.addAttribute("recordList", list)写成了model.addAttribute("recordList ", list)(变量名多了一个空格),导致JSP取不到值。
解决:打开TempCarController.java,搜索recordList(注意末尾空格),删掉空格,重新编译部署。
5.3 现象:修改密码功能点击提交后,页面显示“修改成功”,但数据库sys_user.password字段没变
原因:SystemController.java中updatePassword()方法调用了userService.updatePassword(oldPass, newPass, userId),但UserServiceImpl.java里该方法体是空的,只写了// TODO。
根因:作者忘记实现,或故意留空让学生补全。
解决:在UserServiceImpl.java中补全逻辑:
@Override public void updatePassword(String oldPass, String newPass, Integer userId) { // 1. 先查原密码是否匹配 User user = userDao.selectById(userId); if (!oldPass.equals(user.getPassword())) { throw new RuntimeException("原密码错误"); } // 2. 更新密码 user.setPassword(newPass); userDao.update(user); }5.4 现象:打印页面中文显示为方块(□□□),英文正常
原因:PrintUtil.java生成的HTML中<style>未指定中文字体,浏览器默认用不支持中文的字体渲染。
根因:<style>里只写了font-family:SimSun,但SimSun(宋体)在Mac/Linux上不存在。
解决:修改PrintUtil.java中的<style>,增加通用中文字体栈:
html.append("<style>body{font-family:'Microsoft YaHei','SimSun','sans-serif';font-size:14px;} .print-area{width:80%;margin:0 auto;}</style>");5.5 现象:Tomcat启动后,访问/parking-system/报404,但/parking-system/login.jsp能打开
原因:web.xml中<welcome-file-list>配置缺失,导致根路径/找不到默认首页。
根因:src/main/webapp/WEB-INF/web.xml里没有<welcome-file-list>节点。
解决:在web.xml的</web-app>前添加:
<welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list>6. 二次开发实战:给固定车主加“到期提醒”功能,从需求到上线只需改3个文件
答辩老师最爱问:“如果要加一个XX功能,你怎么实现?” 这里以“固定车主合同到期前7天邮件提醒”为例,演示一个真实、可落地、且能讲清楚技术路径的改造。全程不碰框架,只改业务代码,改完立刻生效。
6.1 需求分析:明确触发条件、数据源和输出动作
- 触发条件:每天凌晨2点检查
fixed_car表中end_date字段,筛选end_date BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 7 DAY)的记录; - 数据源:
fixed_car表(含plate_number,end_date,user_id),关联sys_user表获取操作员邮箱; - 输出动作:调用JavaMail发送提醒邮件,主题“【停车系统】您的固定车位将于X天后到期”。
注意:不引入Quartz等调度框架,用最简方案——
ServletContextListener监听应用启动时,用Timer启动一个每日任务。
6.2 改造步骤:只动3个文件,10分钟搞定
步骤1:加邮件工具类EmailUtil.java(com.parking.system.util包下)
// com.parking.system.util.EmailUtil.java public class EmailUtil { private static final String SMTP_HOST = "smtp.qq.com"; private static final String USERNAME = "your_qq_email@qq.com"; private static final String PASSWORD = "your_smtp_password"; // QQ邮箱需用SMTP专用密码 public static void sendExpirationAlert(String toEmail, String plateNumber, Date endDate) { Properties props = new Properties(); props.put("mail.smtp.host", SMTP_HOST); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.port", "587"); Session session = Session.getInstance(props, new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(USERNAME, PASSWORD); } }); try { Message message = new MimeMessage(session); message.setFrom(new InternetAddress(USERNAME)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); message.setSubject("【停车系统】您的固定车位将于" + daysUntil(endDate) + "天后到期"); message.setText("尊敬的车主:\n您的车牌" + plateNumber + "绑定的固定车位合同将于" + new SimpleDateFormat("yyyy-MM-dd").format(endDate) + "到期,请及时续费。\n\n停车系统自动发送"); Transport.send(message); } catch (Exception e) { e.printStackTrace(); // 生产环境应记日志 } } private static long daysUntil(Date endDate) { long diff = endDate.getTime() - System.currentTimeMillis(); return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS); } }关键点:
PASSWORD不是QQ邮箱登录密码,是QQ邮箱设置里的“SMTP专用密码”,需在邮箱网页端开启SMTP服务并生成。
步骤2:加定时任务监听器StartupListener.java(com.parking.system.listener包下)
// com.parking.system.listener.StartupListener.java @WebListener public class StartupListener implements ServletContextListener { private Timer timer; @Override public void contextInitialized(ServletContextEvent sce) { timer = new Timer("FixedCarExpiryChecker", true); // 每天凌晨2点执行 Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 2); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); // 如果已过今天2点,则设为明天2点 if (calendar.getTimeInMillis() < System.currentTimeMillis()) { calendar.add(Calendar.DATE, 1); } timer.scheduleAtFixedRate(new ExpiryCheckTask(), calendar.getTime(), 24 * 60 * 60 * 1000); } @Override public void contextDestroyed(ServletContextEvent sce) { if (timer != null) timer.cancel(); } }步骤3:加任务类ExpiryCheckTask.java(同包下)
// com.parking.system.listener.ExpiryCheckTask.java public class ExpiryCheckTask extends TimerTask { @Override public void run() { // 1. 获取Spring容器(需在web.xml中配置ContextLoaderListener) WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext(); FixedCarService fixedCarService = context.getBean(FixedCarService.class); // 2. 查询7天内到期的固定车主 List<FixedCar> expiringCars = fixedCarService.findExpiringIn7Days(); // 3. 遍历发送邮件 for (FixedCar car : expiringCars) { // 关联查用户邮箱 User user = context.getBean(UserService.class).selectById(car.getUserId()); if (user != null && user.getEmail() != null && !user.getEmail().trim().isEmpty()) { EmailUtil.sendExpirationAlert(user.getEmail(), car.getPlateNumber(), car.getEndDate()); } } } }最后一步:在
web.xml中注册监听器(确保在<filter>之前):<listener> <listener-class>com.parking.system.listener.StartupListener</listener-class> </listener>
6.3 验证与答辩话术:如何向老师证明你真懂
- 验证:启动Tomcat后,观察控制台是否打印
FixedCarExpiryChecker线程启动日志;手动修改fixed_car.end_date为明天日期,等待任务触发,查收邮箱; - 答辩话术:
“老师,我给固定车主加了到期提醒。核心思路是用
Timer做轻量级调度,不引入复杂框架。数据源来自fixed_car表的end_date字段,通过MyBatis的@Select动态SQL查出7天内到期的记录,再关联sys_user表获取邮箱,用JavaMail发信。所有改动集中在三个类,没动原有业务逻辑,符合开闭原则。”
从那以后我每次接到毕设类项目,都强制走一遍「需求→找数据源→查现有DAO→补Service→加Controller/Listener」这个链路,绝不跳步。因为答辩时老师不会问“Spring Boot自动配置原理”,但一定会盯着你改的那行updateStatusById()问“为什么这里要加事务?不加会怎样?”——答案不在书里,在你亲手跑通并改过的每一行代码里。希望帮到你。
本文还有配套的精品资源,点击获取