
简介一款基于Java、JavaScript与CSS的宿舍报修系统设计源码面向高校学生、初级Java开发者以及物业管理系统从业者可作为课程设计、毕业设计或快速搭建报修业务原型的参考。压缩包共137个文件、约14.14MB主要包含36个class编译类、34个JSP页面、18个java源文件、8个JavaScript脚本、8个JAR依赖库、6个XML配置、4个CSS样式表以及JPG图片、Idea项目配置和Markdown说明能够清晰看到从业务逻辑、数据持久化到页面渲染的完整前后端结构。目前已有114人学习下载。这套源码充分运用Java在后台稳定处理业务逻辑JavaScript在前端实现动态交互CSS统一整体视觉风格并借助JSP将数据动态展示给用户。读者可重点分析Controller、Service、Dao等核心类的调用关系参考JSP与JavaScript的交互写法结合XML配置文件理解数据库连接与部署参数从而快速迁移到自己的项目中有效缩短二次开发或毕设实现路径。其Markdown说明和目录结构能帮助快速定位关键模块尤其适合作为前后端整合开发的入门案例。1. 宿舍报修系统的第一行代码该写在哪——一张报修单的旅程如果把宿舍报修系统拆开看真正值得花时间设计的不是提交报修那个表单而是报修单从待受理到维修中再到已完成的状态流转。这套源码给了我同样的印象135 个文件里真正干活的不是那 8 个 JavaScript而是RepairController、RepairService、RepairDaoImpl这条 Java 链路上对状态的硬约束。适合谁呢一类是正在做高校课程设计的在校生另一类是物业系统里想快速落地一个内部报修模块的开发者。后者尤其要注意不要被 JSP 劝退34 个 JSP 页面在这个项目里承担的是模板职责理解了这一点后面看代码会顺畅很多。2. 后端 Java 层RepairController、RepairService 与 RepairDaoImpl 的请求处理链路2.1 为什么是 Java而不是 Node.js 或 PHP对于宿舍报修这种内部系统Node.js 写起来更快但 Java 在这个场景的优势是类型约束和现成的 MVC 分层。源码里的RepairController.class、StudentController.class、MangerController.class三个类说明了作者采用的是经典 Controller-Service-Dao 三层。这里的MangerController大概率是ManagerController的拼写变体在 IDE 里全局重命名就行不影响运行。三层的好处是当报修流程从提交-完成扩展到派单-回访-超时自动催单时你只需要改 Service 层和 Dao 层Controller 的接口形态不用变。对面试的人来说这也是一个很好的Servlet 生命周期 分层设计的实战样例比单纯背 java 面试八股文更容易讲清楚。这个选型还有一个现实原因课程设计和老牌物业系统大多跑在 Tomcat 上Java Web 部署生态比 Node 成熟。你拿到这套源码后第一步不是急着改页面而是先确认RepairService和RepairDaoImpl之间的接口是否匹配避免出现ClassNotFoundException之后才开始翻代码。如果 IDE 里同时出现了两个相同的.class文件先使用mvn clean或javac重新编译而不是手动删文件。2.2 Controller 只做收口Service 做业务判断JSP 页面提交的报修请求最终落在RepairController的doPost方法里。常见做法是 Controller 只做三件事取参数、调 Service、按返回值跳转或输出 JSON。下面是一个贴近项目真实结构的示意代码。// RepairController.java 核心片段示意 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String dormitory request.getParameter(dormitory); String description request.getParameter(description); String studentId request.getParameter(studentId); // 参数收口校验非空避免脏数据进入 Service if (dormitory null || dormitory.isEmpty() || description null || description.isEmpty()) { response.getWriter().write({\code\:400,\msg\:\宿舍或描述不能为空\}); return; } RepairService service new RepairService(); int result service.submitRepair(studentId, dormitory, description); response.setContentType(application/json;charsetUTF-8); response.getWriter().write(result 0 ? {\code\:200,\msg\:\报修成功\} : {\code\:500,\msg\:\写入失败\}); }上面的代码反映了三层职责Controller 负责解析 HTTP 参数和响应格式RepairService承担这个学生是否被禁用、今日是否已提交过同类报修这类业务判断真正的 SQL 操作在RepairDaoImpl。注意doPost开头必须设置 UTF-8 编码不然宿舍楼栋名里的中文会变成乱码。参数说明dormitory对应 JSP 表单里的宿舍号输入框description是故障描述studentId可以从 session 里取而不是前端传。如果前端直接传studentId任何人都能冒充他人提交报修这是安全意识上最常见的缺口。另外response.setContentType里的charsetUTF-8不是可选项少了它前端拿到的 JSON 中文会乱码。2.3 RepairDaoImpl 的 SQL 与事务控制RepairDaoImpl是数据访问实现类。源码里它可能直接使用 JDBC 或者 Spring JDBC Template。如果是前者事务要手工控制如果是后者Transactional注解就够。但无论哪种报修单的插入必须和状态初始化放在同一事务里否则会出现单子建好了状态却是空的这种问题。// RepairDaoImpl 插入报修单示意 public int insertRepair(Repair repair) { String sql INSERT INTO repair_order(student_id, dormitory, description, status, create_time) VALUES(?, ?, ?, 待受理, NOW()); try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, repair.getStudentId()); ps.setString(2, repair.getDormitory()); ps.setString(3, repair.getDescription()); return ps.executeUpdate(); } catch (SQLException e) { // 日志里要带参数方便排查 return 0; } }这段代码有两个关键参数第一个是status字段的默认值建议用中文枚举值「待受理 / 维修中 / 已完成 / 已取消」而不是数字因为 JSP 页面直接展示这个字段数字会导致前端还要做一次映射。第二个是NOW()它依赖数据库时钟如果应用服务器和数据库服务器不在同一个机房时间会漂移定时催单任务要特别注意。在catch里建议使用日志框架记录repair.getDormitory()这样的业务参数否则生产环境出现问题只能看到一个空异常栈。另外源码中RepairDaoImpl.class被重复列出这通常不是重复开发而是编译产物里同时存在内部类或者项目目录被打包了两份。拿到代码后先清理target或out目录再重新编译避免 IDE 编译时取到旧 class 导致行为不一致。2.4 从 36 个 Java class 文件反推模块边界整个源码有 36 个 class 文件和 18 个 Java 源文件按模块划分大约是学生端提交报修、管理员查看/派单、维修工更新状态、系统基础配置。StudentController管学生登录和个人报修列表MangerController管后台管理RepairService和RepairDaoImpl是核心业务载体。这个边界符合绝大多数宿舍管理系统的原型不需要复杂权限框架一个user_type字段加简单的 session 判断就能跑起来。对于想拿这套代码做二次开发的人建议先把RepairService里的方法列成接口清单再去看 Dao 实现。比如submitRepair、listByStudentId、listAllPending、updateStatus这些方法名可以直接映射到 JSP 页面的功能点。这样比逐行读代码快得多也能避免被重复的 class 文件带偏。如果RepairService的方法返回值是int建议改成有意义的 Java 对象或RepairResult否则前端只能靠约定 1 表示成功、0 表示失败可读性很差。3. 前端 JSP JavaScript CSS报修表单的动态校验与状态轮询3.1 JSP 不是退役技术它只是换了一种活法34 个 JSP 页面在这个项目里扮演的是模板角色服务端渲染出 HTML再由 JavaScript 增强交互。和现在流行的前后端分离相比JSP 的好处是页面首次加载就能拿到数据不需要额外请求/api/detail。坏处是如果 Java 代码混在% %里页面会越来越难维护。所以拿到源码后第一件事是搜索 JSP 里是否还有System.out.println看到就换成日志否则输出会污染 HTTP 响应体导致浏览器解析出一段奇怪的文本。还有一点容易被忽略JSP 页面里${}和 JavaScript 模板字符串${} 会发生冲突如果项目用了 EL 表达式JS 里必须改用字符串拼接或号否则后端会把它当成 EL 表达式处理。3.2 JavaScript 负责提交前校验与不刷新更新8 个 JavaScript 文件在报修场景里主要负责两件事表单提交前校验、以及异步刷新状态。下面是一个用 fetch 提交报修单的例子对应RepairController的/repair/submit接口。// repair-form.js 片段 document.getElementById(submitBtn).addEventListener(click, function (event) { event.preventDefault(); const dorm document.getElementById(dormitory).value.trim(); const desc document.getElementById(description).value.trim(); if (dorm.length 3 || desc.length 5) { document.getElementById(tip).textContent 宿舍号或故障描述太短; return; } const formData new URLSearchParams(); formData.append(dormitory, dorm); formData.append(description, desc); fetch(/repair/submit, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded;charsetUTF-8 }, body: formData.toString() }) .then(res res.json()) .then(data { if (data.code 200) { document.getElementById(list).innerHTML 报修成功等待维修工接单; } else { document.getElementById(tip).textContent data.msg; } }) .catch(err { document.getElementById(tip).textContent 网络异常请重试; }); });这里要注意两个参数Content-Type必须和后端request.getParameter的解析方式一致。用application/x-www-form-urlencoded时URLSearchParams会自动做编码如果用application/json后端需要额外从request.getReader()里读流简单的getParameter会全部拿到null。另一个参数是 fetch 默认不会带上 Cookie如果RepairController里有getSession()的判断必须加credentials: same-origin否则用户登录状态永远传不过去。另外源码里 JS 文件数量只有 8 个说明作者把公共函数收敛到了一个文件里比如状态轮询。如果你要做轮询不要用setInterval每隔 5 秒发一遍请求页面切走再回来会堆积一堆定时器。建议监听visibilitychange事件只在页面回到前台时刷新一次。这在移动端尤其重要。3.3 CSS 让状态卡片在不同分辨率下不塌4 个 CSS 样式表在这个系统里主要负责报修卡片、按钮、状态标签。最值得学习的是状态标签的配色不要每个类名单独写颜色用 CSS 变量收敛会让后续维护轻松很多。下面是一段可以直接套用的代码。/* repair.css 片段 */ :root { --status-pending: #e6a23c; --status-processing: #409eff; --status-done: #67c23a; --status-cancel: #909399; } .status-tag { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 12px; color: #fff; } .status-tag[data-status待受理] { background: var(--status-pending); } .status-tag[data-status维修中] { background: var(--status-processing); } .status-tag[data-status已完成] { background: var(--status-done); } .status-tag[data-status已取消] { background: var(--status-cancel); }这段代码把状态值和背景色做了一个显式映射比用具类名status-wait更好维护。如果你想加一点交互把.status-tag设置成transition: background 0.2s;再给待受理状态加cursor: pointer配合一个简单的:hover伪类选择器就能让后台管理员知道这个标签可以点击。CSS 伪类选择器在这个场景里恰好能派上用场。需要注意JSP 循环输出报修单时>!-- web.xml 片段 -- servlet servlet-nameRepairController/servlet-name servlet-classcom.dorm.repair.controller.RepairController/servlet-class /servlet servlet-mapping servlet-nameRepairController/servlet-name url-pattern/repair/submit/url-pattern /servlet-mapping这里的url-pattern必须和前端 fetch 的地址保持一致。很多 404 出现就是因为改动了前端路径但忘了改web.xml。另一个容易错的是servlet-class全限定名如果你把类从controller包挪到了web包这里的全限定名不更新Tomcat 启动时就会报ClassNotFoundException而且日志经常被其他启动信息淹没需要单独过滤。数据源配置一般在jdbc.properties或context.xml里核心参数如下表。注意符号在 XML 里必须转义否则配置文件解析失败。参数示例值作用jdbc.drivercom.mysql.cj.jdbc.DriverMySQL 驱动类名jdbc.urljdbc:mysql://localhost:3306/dorm?useUnicodetruecharacterEncodingutf8连接串编码参数决定中文是否乱码jdbc.usernameroot数据库帐号jdbc.password****数据库密码dbcp.initialSize5连接池初始化连接数注意useUnicodetruecharacterEncodingutf8中的在 XML 文件中需要写成amp;否则 XML 直接解析失败。如果你在spring-mvc.xml里配数据源同样要遵守这个规则。另外characterEncodingutf8并不是万能的数据库表本身的charset也要是utf8mb4否则依然会乱码这个坑经常在报修描述里的 emoji 表情上爆发。4.3 JAR 库文件与 Java 版本8 个 JAR 背后是 JDK 8 还是 11源码里有 8 个 JAR 库文件对应的一般是 MySQL 驱动、Servlet API、JSTL、日志库等。拿到手后不要直接复制到 Tomcat 的 lib 目录先看项目里 Java 源码的编译目标。如果是 JDK 8String相关的 API 都正常如果项目中出现过var关键字或List.of那必须用 JDK 11 以上重新编译否则运行时会报UnsupportedClassVersionError。这个错误在 Tomcat 日志里表现为java.lang.UnsupportedClassVersionError: com/dorm/repair/RepairController has been compiled by a more recent version of the Java Runtime解决办法不是改 Tomcat 版本而是把 IDE 的 Project Structure 里的 SDK 版本和pom.xml如果有里的maven.compiler.source、maven.compiler.target对齐。如果项目没有使用 Maven只能用javac --release 8重新编译。注意--release和-source、-target的区别--release会同时限制可用的 JDK API防止你用到高版本才有的方法。4.4 部署到 Tomcat 报 404/500 的排错顺序404 和 500 是部署后最常见的两类错误。我的排错顺序是先看 Tomcat 的catalina.out再确认web.xml映射最后看数据库连接。如果日志里出现Table dorm.repair_order doesnt exist那就是建表脚本没执行而不是代码问题这时候没必要去前端改 fetch 地址。# 查看 Tomcat 最近日志 tail -n 100 /usr/local/tomcat/logs/catalina.out # 按关键字过滤异常 grep -i exception /usr/local/tomcat/logs/catalina.out | tail -20日志里如果看到Communications link failure说明数据库连不上优先检查jdbc.url里的端口是否和 MySQL 实际端口一致。如果看到Data truncation说明某个字段长度小于输入值需要去表结构里调整varchar长度。如果看到IllegalStateException那通常是响应已经被 JSP 输出后又在 Java 里getWriter典型原因是 JSP 前段用了out.flush()需要把输出 JSON 的逻辑放到 JSP 之前或者改用out.clear()清空缓冲区。对于有运维经验的人来说把这三类异常记录成一个简单的排错清单比每次临时搜日志更快。5. 进阶把报修状态机抽出来用 Timer 自动催单5.1 状态机四个状态、三种合法迁移报修单状态最好在 Service 里定义成枚举避免字符串散落各处。合法的迁移只有三种待受理 - 维修中 - 已完成待受理 - 已取消以及维修中 - 已取消。在RepairService里加一个changeState(orderId, fromStatus, toStatus)方法更新 SQL 时带上where status ?条件利用数据库行锁防止并发覆盖。这样一个简单的状态机就能拦截住已完成 - 待受理这类非法操作而不是靠前端按钮控制。5.2 用 ScheduledExecutorService 扫描超时订单Java 原生的Timer是单线程的一次任务卡住就会导致后续任务不执行所以更推荐ScheduledExecutorService。下面是一个简化版的超时扫描任务把超过 24 小时还在待受理的单子自动取消。public class RepairTimeoutTask implements Runnable { private final RepairDao repairDao new RepairDaoImpl(); Override public void run() { ListRepair expired repairDao.findExpiredRepairs(待受理, 24); for (Repair r : expired) { repairDao.changeState(r.getId(), 待受理, 已取消); } // 生产环境记录处理条数log.info(timeout repair count{}, expired.size()); } }这里findExpiredRepairs的 SQL 核心是status ? AND create_time NOW() - INTERVAL ? HOUR。参数是状态和超时时间按学校维修 SLA 调整。注意ScheduledExecutorService的线程池大小设置成 1 就够了但要设置setRemoveOnCancelPolicy(true)否则已取消的任务会堆积在队列里。RepairDaoImpl里的changeState必须返回受影响行数如果返回 0 说明状态已经被别人改过当前线程需要放弃操作。5.3 验证方式造数据 观察日志为了验证催单逻辑可以手动在数据库里把一条记录的create_time改成 25 小时前然后手动触发RepairTimeoutTask.run()或者通过调度器等待下一次执行。观察日志里是否出现timeout repair count1再去库里查询状态是否为已取消。如果改造前的代码用的是update repair_order set status已取消 where id?而不带current_status条件那么学生已经在维修中的单子也会被误取消因为扫描条件只看了create_time没看status。所以changeState里加上where status 待受理是这一步的关键否则整个定时任务就是一个定时删库任务。本文还有配套的精品资源点击获取