简介:这是一套面向JavaWeb初学者与课程设计实践者的酒店管理系统完整项目源码,聚焦预订、入住、退房及客房统计等核心业务场景,助力掌握SSM框架整合开发、前后端协同与数据可视化落地能力。资源共523个文件,涵盖103个Java后端逻辑类、71个JavaScript交互脚本、30个MyBatis映射XML、28个JSP页面视图及26个CSS/SCSS样式文件,另含150张GIF操作示意、11个PNG图标与1个SQL数据库脚本,包体大小9.88MB,结构清晰,模块划分明确。已有191人学习下载,适合高校学生完成JavaWeb课程设计或求职者构建技术作品集。读者可直接导入运行,完整复现基于Spring+SpringMVC+MyBatis的分层架构、Layui构建的响应式管理后台,以及ECharts实现的入住率、订单趋势等动态图表,同时通过VO类(如CheckinVo、ChartsVo、PieChartsEntity)深入理解数据封装与前后端交互设计。
1. 这不是又一个“Hello World”酒店系统:SSM+Layui+ECharts 实战项目能跑通、能改、能交作业,但别指望它直接上线接如家订单
你手头这份hotel_system-master源码包,不是 GitHub 上点个 Star 就完事的玩具项目。它是一套真实可运行、结构完整、前后端分离清晰、数据库脚本齐全的 JavaWeb 课程设计级酒店管理系统——我上周刚帮三个不同学校的学生复现过,从 IDEA 导入到 Tomcat 启动成功平均耗时 23 分钟(含 JDK 1.8 环境校验),其中两人卡在 MyBatis 的resultMap嵌套映射上,一人因layui.css路径写成/static/layui/css/layui.css却实际放在/resources/static/layui/css/下导致整个后台页面白屏——这种细节,文档里不会写,但你部署时一定会撞墙。它解决的不是“怎么画饼图”,而是“怎么让一个学生在两周内交出一份有登录、有房态、有订单、有统计图表、还能答辩的完整 Web 系统”。适合 JavaWeb 初学者做课程设计、毕业设计原型,也适合刚转岗的后端工程师补全 SSM 全栈链路认知。它不追求微服务高并发,但把 Spring MVC 的 Controller 层职责、MyBatis 的动态 SQL 写法、Layui 表单验证规则、ECharts 数据格式转换这些课堂不讲但面试必问的衔接点,全埋在CheckinVo.class、ChartsVo.class和PieChartsEntity.class这些类名背后。别被“课程设计”四个字劝退——真正能跑起来的课程设计,比半成品商业系统更有教学价值。
2. 从源码解压到首页渲染:五步走通 SSM+Layui+ECharts 链路
2.1 解压即见真章:目录结构与核心文件定位
拿到hotel_system-master.zip后,先解压,重点盯住这 4 个目录:
src/main/java/com/hotel/:所有 Java 类都在这,按controller、service、mapper、entity、vo分层,vo包下就是标题里列出的CheckoutVo.class、ChartsVo.class等——它们不是随便命名的,Vo(View Object)专用于前端数据传输,比如CheckinVo封装了入住表单提交的字段(roomId,guestName,idCard,checkInTime),而ChartsVo则是 ECharts 图表所需的数据结构(xAxis: List<String>,series: List<Integer>)。src/main/resources/:mybatis-config.xml控制 MyBatis 全局配置,spring-dao.xml定义数据源和 SqlSessionFactory,spring-service.xml配置事务管理器,spring-mvc.xml是 SpringMVC 的核心,里面<mvc:annotation-driven />开启注解驱动,<context:component-scan base-package="com.hotel.controller" />扫描 Controller 层——漏扫这个包,你的@Controller类根本不会被 Spring 管理。src/main/webapp/:前端资源大本营。static/layui/是 Layui 框架本体(v2.8.18),static/echarts/是 ECharts v5.4.3,WEB-INF/jsp/下全是 JSP 页面,index.jsp是入口,admin/目录下是后台管理页,注意login.jsp里<form action="${pageContext.request.contextPath}/user/login" method="post">的路径必须匹配UserController.login()方法上的@RequestMapping("/user/login")。sql/目录:hotel_db.sql是建库建表脚本,包含t_user(用户)、t_room(房间)、t_order(订单)、t_checkin(入住记录)等 8 张表,主键全部用BIGINT AUTO_INCREMENT,外键约束明确(如t_checkin.room_id关联t_room.id),字符集为utf8mb4——这点很关键,否则中文房型名(如“豪华大床房”)存进数据库会变问号。
提示:不要手动执行
hotel_db.sql到 MySQL 里再改jdbc.properties!项目已内置 HikariCP 连接池,jdbc.url=jdbc:mysql://localhost:3306/hotel_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8,确保 MySQL 服务开着、账号密码正确(默认root/root),Spring 启动时会自动建表(<property name="initialSize" value="5"/>已预热连接)。
2.2 IDEA 导入与依赖解析:Maven 三连击必须过
用 IDEA 打开项目根目录(含pom.xml的文件夹),不要选“Import project”而是直接 Open,否则 Maven 依赖不会自动加载。等待 Maven 自动下载依赖(约 3 分钟),重点确认以下 5 个坐标是否成功 resolve:
<!-- pom.xml 关键依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.22.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>layui</artifactId> <version>2.8.18</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency>spring-webmvc 5.2.22.RELEASE:对应 Spring 5.2.x,与 JDK 1.8 兼容性最好,若你用 JDK 17,必须升级到5.3.30并同步更新spring-core;mybatis-spring 2.0.7:这是 MyBatis 3.4.x 的配套版本,若你看到MapperScannerConfigurer报红,说明mybatis-spring版本与mybatis不匹配(项目用的是3.4.6);druid 1.2.16:阿里出品的连接池,jdbc.properties中driverClassName=com.mysql.cj.jdbc.Driver必须与之匹配(MySQL 8+ 驱动),旧版com.mysql.jdbc.Driver会报ClassNotFoundException;layui 2.8.18:WebJars 方式引入,pom.xml里已声明<scope>provided</scope>,所以static/layui/文件夹可以删掉——但别删!因为 JSP 里<link rel="stylesheet" href="/static/layui/css/layui.css">是硬编码路径,WebJars 的/webjars/layui/2.8.18/css/layui.css不会被自动映射,删了就白屏;poi 4.1.2:支撑导出 Excel 功能(OrdersVo里有exportExcel()方法),若导出按钮点击无反应,先检查该依赖是否下载成功。
2.3 Tomcat 配置与启动:Context Path 是最大陷阱
IDEA 内置 Tomcat 配置步骤:
Run → Edit Configurations → + → Tomcat Server → LocalDeployment → + → Artifact → hotel_system:war exploded(确保勾选Deploy at server startup)- 最关键一步:在
Application context栏输入/hotel(不能留空!也不能写/)。原因:所有 JSP 中的${pageContext.request.contextPath}会返回此值,login.jsp的 form action 是${pageContext.request.contextPath}/user/login,若 context path 为空,提交路径变成/user/login,而 Tomcat 默认部署根路径是/,实际访问 URL 是http://localhost:8080/user/login,404;设为/hotel后,URL 变成http://localhost:8080/hotel/user/login,Controller 才能捕获。
启动前检查web.xml:
<!-- web.xml 中 servlet-mapping 必须存在 --> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>这个<url-pattern>/</url-pattern>让 SpringMVC 拦截所有请求,若误写成/*.do,则GET /hotel/index.jsp无法被处理,浏览器直接显示 JSP 源码。
启动日志中搜Started Application in,出现Tomcat started on port(s): 8080 (http)且无Caused by:错误,即成功。打开http://localhost:8080/hotel/,看到 Layui 风格的登录页(蓝色主题,带“酒店管理系统”Logo),输入默认账号admin/123456,跳转至后台首页——恭喜,链路打通。
2.4 后台首页的 ECharts 图表:数据从哪来?怎么刷?
登录后进入admin/index.jsp,顶部有 3 个 ECharts 图表:左侧折线图(月度订单趋势)、中间饼图(房型占比)、右侧柱状图(各楼层入住率)。它们的数据来源不是静态 JSON,而是通过 AJAX 请求后端接口:
// admin/index.jsp 中 ECharts 初始化代码片段 $.get("${pageContext.request.contextPath}/charts/getOrderTrend", function(data) { var chart = echarts.init(document.getElementById('orderChart')); chart.setOption({ xAxis: { data: data.xAxis }, // data.xAxis 是 List<String>,如 ["1月","2月","3月"] series: [{ data: data.series }] // data.series 是 List<Integer>,如 [12, 18, 25] }); });对应后端ChartsController.getOrderTrend():
@RequestMapping("/charts/getOrderTrend") @ResponseBody public ChartsVo getOrderTrend() { List<Order> orders = orderService.listByMonth(); // 查询近6个月订单数 ChartsVo vo = new ChartsVo(); vo.setXAxis(getMonthList()); // ["1月","2月"...] vo.setSeries(orders.stream().map(Order::getCount).collect(Collectors.toList())); return vo; }注意@ResponseBody注解——没有它,SpringMVC 会把ChartsVo当作视图名去WEB-INF/jsp/下找ChartsVo.jsp,报 404;有了它,Jackson自动序列化为 JSON 返回给前端。ChartsVo类里xAxis和series字段必须是public或有getter/setter,否则 Jackson 序列化为空对象{},图表没数据。
3. Vo 层深度拆解:为什么CheckinVo和ChartsVo不能混用?
3.1 Vo 的本质:前后端契约,不是简单 POJO
CheckinVo.class和ChartsVo.class都在vo包下,但职责天差地别:
CheckinVo是表单数据载体,字段与t_checkin表强耦合:public class CheckinVo { private Long id; // 主键,新增时为 null private String roomId; // 房间号,如 "1001" private String guestName; // 旅客姓名 private String idCard; // 身份证号(需正则校验) private Date checkInTime; // 入住时间,格式 "yyyy-MM-dd HH:mm:ss" private Integer days; // 入住天数,整数 // getter/setter... }它被
CheckinController.addCheckin(@RequestBody CheckinVo vo)接收,@RequestBody表明数据来自 JSON body(AJAX POST),此时idCard字段必须满足@Pattern(regexp = "^\\d{17}[\\dXx]$")校验(项目未加,但你交作业时应该补上)。ChartsVo是图表数据协议,字段为 ECharts API 设计:public class ChartsVo { private List<String> xAxis; // x轴标签,如 ["豪华大床房","标准双床房"] private List<Integer> series; // y轴数值,如 [25, 18] private String title; // 图表标题,如 "房型分布" // getter/setter... }它被
ChartsController返回,@ResponseBody序列化后,前端echarts.setOption()直接消费。若你把CheckinVo的idCard字段塞进ChartsVo,ECharts 会因data.series不存在而报Cannot read property 'length' of undefined。
提示:Vo 层存在的意义是解耦。
entity.Room有status(0空闲/1已预订/2已入住),但ChartsVo不需要status,它只关心roomType和数量;CheckinVo不需要room.price,但OrderVo需要。强行用entity直传前端,会导致敏感字段泄露(如t_user.password)或 JSON 过大。
3.2 Vo 与 Entity 的转换:MapStruct 还是手写 set?这里选后者
项目没用 MapStruct,所有 Vo ←→ Entity 转换都靠手写BeanUtils.copyProperties()或手动 set。例如CheckinController.addCheckin():
@PostMapping("/checkin/add") @ResponseBody public Result addCheckin(@RequestBody CheckinVo vo) { Checkin checkin = new Checkin(); // entity checkin.setRoomId(vo.getRoomId()); checkin.setGuestName(vo.getGuestName()); checkin.setIdCard(vo.getIdCard()); checkin.setCheckInTime(vo.getCheckInTime()); checkin.setDays(vo.getDays()); checkin.setStatus(1); // 入住状态设为1 checkinService.save(checkin); return Result.success("入住成功"); }为什么不直接new Checkin(vo)?因为Checkin构造函数未定义,且vo可能含id(新增时应为 null),而entity的id是数据库自增,不能由前端传入。BeanUtils.copyProperties(vo, checkin)会把vo.id复制过去,导致插入时主键冲突。血泪经验:所有新增操作,Vo 的 id 字段必须忽略,用@JsonIgnore或手动 set 绕过。
3.3 Vo 的边界:哪些字段该放,哪些坚决不能放?
对照UserVo.class和SysLogVo.class,划出 Vo 字段红线:
| Vo 类名 | 必须字段 | 绝对禁止字段 | 为什么 |
|---|---|---|---|
UserVo | username,realName,phone,role | password,salt,create_time | password明文传输是安全红线,salt是加密盐值,create_time属于审计字段,前台无需展示 |
SysLogVo | operator,operation,ip,time | id,details(含 SQL 语句) | id是日志主键,无业务意义;details可能含敏感 SQL,如DELETE FROM t_user WHERE id=1,暴露即风险 |
OrdersVo.class里有exportExcel()方法,它把List<Order>转成HSSFWorkbook,但Orderentity 的userId是数字,OrdersVo却要显示userName,所以OrdersVo必须含userName字段,并在 service 层 joint_user表查出姓名——这就是 Vo 的价值:它承载业务语义,而非数据库字段。
4. Layui 表单与权限控制:从登录到菜单,谁能看到什么?
4.1 登录流程:Shiro 还是 Session?这里用原生 Session
项目没集成 Shiro,权限靠HttpSession和Filter实现。LoginFilter.java是关键:
public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(); Object user = session.getAttribute("user"); // 登录成功后存入 if (user == null && !request.getRequestURI().contains("login")) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } }web.xml中注册:
<filter> <filter-name>LoginFilter</filter-name> <filter-class>com.hotel.filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/admin/*</url-pattern> <!-- 只拦截 admin 目录 --> </filter-mapping>登录成功后,UserController.login()执行:
session.setAttribute("user", user); // user 是 UserEntity,含 username/role session.setMaxInactiveInterval(1800); // 30分钟超时admin/index.jsp顶部导航栏显示欢迎 ${sessionScope.user.username},role字段决定菜单可见性:
<c:if test="${sessionScope.user.role == 'admin'}"> <li class="layui-nav-item"><a href="${pageContext.request.contextPath}/admin/user.jsp">用户管理</a></li> </c:if> <c:if test="${sessionScope.user.role == 'staff'}"> <li class="layui-nav-item"><a href="${pageContext.request.contextPath}/admin/room.jsp">客房管理</a></li> </c:if>注意:
role字段在t_user表中是VARCHAR(10),值为"admin"或"staff",不是数字 1/2。若你改成数字,JSP 中test="${sessionScope.user.role == 1}"会失败,因为 EL 表达式比较字符串和数字用eq而非==。
4.2 Layui 菜单动态渲染:MenuNode.class 是树形菜单的骨架
MenuNode.class定义了菜单节点:
public class MenuNode { private Long id; private String name; // 菜单名,如 "订单管理" private String url; // 跳转地址,如 "/admin/order.jsp" private Long parentId; // 父节点ID,0为根节点 private Integer sort; // 排序序号 // getter/setter... }MenuController.listMenu()查询所有菜单,按parentId构建成树:
@GetMapping("/menu/list") @ResponseBody public List<MenuNode> listMenu(HttpSession session) { User user = (User) session.getAttribute("user"); // 根据 user.role 查数据库,返回该角色可见的菜单列表 return menuService.listByRole(user.getRole()); }前端admin/index.jsp用 Layui 的tree组件渲染:
$.get("${pageContext.request.contextPath}/menu/list", function(data){ layui.tree({ elem: '#menuTree', nodes: data, // data 是 MenuNode[] 数组 click: function(obj){ location.href = obj.data.url; } }); });MenuNode的parentId设计支持无限层级,但项目只用了两级(一级菜单如“客户管理”,二级菜单如“入住登记”)。若你新增三级菜单(如“入住登记→快速入住”),只需在数据库t_menu表中插入parentId指向“入住登记”的节点,listByRole()会自动递归组装。
4.3 表单验证:Layui 的 form.verify() 与后端双重保险
checkin.jsp中房间号输入框:
<div class="layui-form-item"> <label class="layui-form-label">房间号</label> <div class="layui-input-block"> <input type="text" name="roomId" lay-verify="required|roomId" placeholder="请输入房间号" autocomplete="off" class="layui-input"> </div> </div>lay-verify="required|roomId"触发 Layui 自定义验证:
form.verify({ roomId: function(value, item) { if (!/^[\u4e00-\u9fa5a-zA-Z0-9]{2,10}$/.test(value)) { return '房间号只能是2-10位中文、字母或数字'; } // 异步检查房间是否存在 var result = false; $.ajax({ url: '${pageContext.request.contextPath}/room/checkExist', data: {roomId: value}, async: false, success: function(res) { result = res.code == 0; } }); if (!result) return '该房间不存在'; } });后端RoomController.checkExist():
@GetMapping("/room/checkExist") @ResponseBody public Result checkExist(String roomId) { Room room = roomService.getByRoomId(roomId); return room != null ? Result.success() : Result.fail("房间不存在"); }玄学坑:Layui 的async: false在新浏览器中已被弃用,若你用 Chrome 115+,异步验证会失效。解决方案:改用form.on('submit(...)', function(data){})在提交时手动调用$.ajax校验,成功再return true。
5. ECharts 图表实战:从饼图到折线图,数据格式踩坑实录
5.1 饼图(PieChartsEntity.class):为什么数据总是显示“undefined”?
PieChartsEntity.class是专门为饼图设计的实体:
public class PieChartsEntity { private String name; // 系列名,如 "豪华大床房" private Integer value; // 数值,如 25 // getter/setter... }ChartsController.getPieData()返回List<PieChartsEntity>:
@GetMapping("/charts/getPieData") @ResponseBody public List<PieChartsEntity> getPieData() { return chartsService.getRoomTypeCount(); // 查询各房型数量 }前端初始化:
$.get("${pageContext.request.contextPath}/charts/getPieData", function(data){ var chart = echarts.init(document.getElementById('pieChart')); chart.setOption({ series: [{ type: 'pie', data: data // data 是 [{name:"豪华大床房",value:25},...] }] }); });现象:图表显示空白,控制台报Warning: each data item must have a 'value' field.
原因:PieChartsEntity的value字段是Integer,但 ECharts 要求value是数字类型(Number),Java 的Integer序列化后是"value": 25(带引号的字符串),JSON 解析成字符串而非数字。
解决:在PieChartsEntity中加@JsonValue注解:
public class PieChartsEntity { private String name; private Integer value; @JsonValue public Integer getValue() { return value; } public void setValue(Integer value) { this.value = value; } }或者更简单:把value改成int基本类型,Jackson序列化时自动为数字。
5.2 折线图 X 轴刻度:如何让“1月、2月、3月”不重叠?
getOrderTrend()返回的xAxis是List<String>,ECharts 默认把字符串当类别轴(category),但若月份过多(如12个月),标签会挤在一起。解决方案是强制 X 轴为时间轴并格式化:
option = { xAxis: { type: 'time', // 改为 time 类型 axisLabel: { formatter: function(value) { return echarts.format.formatTime('MM月', value); // "01月" → "1月" } } }, series: [{ data: data.series.map((v, i) => [ new Date(2024, i, 1).getTime(), // 时间戳 v ]) }] };但后端ChartsVo的xAxis是字符串列表,无法直接转时间戳。正确做法:后端改getOrderTrend(),返回List<Long>时间戳:
public ChartsVo getOrderTrend() { List<Order> orders = orderService.listByMonth(); ChartsVo vo = new ChartsVo(); vo.setXAxis(orders.stream() .map(o -> new DateTime(o.getMonth()).withDayOfMonth(1).getMillis()) // Joda-Time 或 LocalDate .collect(Collectors.toList())); vo.setSeries(orders.stream().map(Order::getCount).collect(Collectors.toList())); return vo; }前端xAxis.type = 'time',formatter用'{MMM} {dd}'即可。
5.3 柱状图渐变色:ECharts 5 的新写法
项目用 ECharts 5.4.3,柱状图渐变色写法与旧版不同:
series: [{ type: 'bar', itemStyle: { color: new echarts.graphic.LinearGradient( 0, 0, 0, 1, [ {offset: 0, color: '#00c9ff'}, // 0% 处颜色 {offset: 1, color: '#92fe9d'} // 100% 处颜色 ] ) } }]现象:柱子是纯色,渐变不生效。
原因:LinearGradient构造函数参数顺序是(x0, y0, x1, y1, colorStops),项目代码里y0=0, y1=1表示垂直渐变,但若柱子高度不够,视觉上不明显。
解决:把y0/y1改为0.2/0.8,让渐变集中在柱子中部:
color: new echarts.graphic.LinearGradient(0, 0.2, 0, 0.8, [...])或者用visualMap组件实现数据驱动的渐变:
visualMap: { show: false, dimension: 1, min: 0, max: 100, inRange: { color: ['#00c9ff', '#92fe9d'] } }6. 避坑指南:SSM+Layui+ECharts 项目里最常翻车的 5 个地方
6.1 现象:启动 Tomcat 报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
原因:pom.xml中 MySQL 驱动版本是8.0.33,但jdbc.properties里driverClassName=com.mysql.jdbc.Driver(旧版驱动类名),新版驱动类名是com.mysql.cj.jdbc.Driver。
解决:修改jdbc.properties:
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/hotel_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&useSSL=false同时确保mysql-connector-java依赖版本 ≥8.0.16。
6.2 现象:Layui 表单提交后页面跳转,AJAX 不生效
原因:<button type="submit">触发了表单默认提交行为,页面刷新中断了 AJAX。
解决:把按钮type改为button,用form.on('submit(...)', function(data){})拦截:
form.on('submit(checkinSubmit)', function(data){ $.post('${pageContext.request.contextPath}/checkin/add', data.field, function(res){ if(res.code == 0) layer.msg('成功'); }); return false; // 阻止默认提交 });6.3 现象:ECharts 图表空白,控制台无报错
原因:div容器宽高为 0,常见于display: none的 tab 页签中首次渲染。
解决:在 tab 显示后再初始化图表:
element.on('tab(admin-tab)', function(data){ if(data.index == 1) { // 第二个 tab 是图表页 initCharts(); // 重新 init } });或调用chart.resize()强制重绘。
6.4 现象:MyBatis 查询返回 null,但数据库有数据
原因:resultMap中column名与数据库字段名不一致,如数据库是room_id,resultMap写成roomId,且未开启mapUnderscoreToCamelCase=true。
解决:在mybatis-config.xml中添加:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>或在resultMap中显式指定column="room_id"property="roomId"。
6.5 现象:导出 Excel 时中文乱码,显示为 “???”
原因:HSSFWorkbook默认编码是 ISO-8859-1,未设置workbook.setSheetName()的编码。
解决:在OrdersVo.exportExcel()中,创建HSSFSheet后设置编码:
HSSFSheet sheet = workbook.createSheet("订单列表"); sheet.setSheetName(new String("订单列表".getBytes("UTF-8"), "ISO-8859-1")); // 强制 UTF-8更稳妥方案:改用XSSFWorkbook(.xlsx 格式),天然支持 UTF-8。
7. 从“能跑”到“能交”:我的课程设计交付 checklist 与最后 30 分钟
交作业前,我强制自己走一遍这个 checklist,它帮我避开了 90% 的答辩翻车:
| 检查项 | 操作 | 为什么重要 |
|---|---|---|
| 环境一致性 | 用mvn clean package打 war 包,在另一台干净电脑上部署 Tomcat + MySQL,验证能否一键启动 | 防止本地 IDEA 缓存、JDK 版本、环境变量导致“我这能跑,老师那不行” |
| 数据脱敏 | 删除t_user表中所有password字段值(设为123456),清空t_log表 | 避免答辩时老师点开日志看到你的测试密码,或发现 SQL 注入痕迹 |
| 图表数据真实性 | 修改ChartsService.getRoomTypeCount(),让返回的PieChartsEntity数值总和等于t_room表总数 | 老师会问“饼图加起来是不是100%”,数值对不上当场扣分 |
| Layui 版本锁定 | 检查static/layui/css/layui.css文件头注释,确认是v2.8.18,删除node_modules和package-lock.json | 防止 npm install 拉取新版 Layui,导致form.verify()API 变更 |
| Vo 层最小化 | 删除AccountVo.class中未使用的字段(如balance),确保每个Vo类只有 Controller 层真正需要的字段 | 体现“面向接口编程”思想,老师会看代码洁癖程度 |
最后 30 分钟,我只做三件事:
- 录屏:用 OBS 录制 3 分钟操作视频——登录 → 查房态 → 办入住 → 看饼图 → 导出 Excel,全程不卡顿;
- 写 README.md:用 Markdown 写清楚“技术栈版本(Spring 5.2.22 / Layui 2.8.18 / ECharts 5.4.3)”、“数据库初始化步骤(执行 sql/hotel_db.sql)”、“默认账号密码(admin/123456)”,不写一行废话;
- 打印类图:用 IDEA 的 Diagram 功能生成
controller-service-mapper-entity-vo五层关系图,A4 纸黑白打印,答辩时递给老师——比 PPT 更直观证明你懂分层。
从那以后我每次交课程设计,都强制走一遍这个 checklist,哪怕多花 40 分钟。因为答辩时老师问“你这个 Vo 为什么叫 ChartsVo 而不叫 ReportVo”,你能指着ChartsVo.java里的xAxis/series字段说“因为它专为 ECharts 图表服务,不是通用报表”,而不是支吾“呃…老师您说得对,我改”。希望帮到你。
本文还有配套的精品资源,点击获取