资讯详情

宿舍报修系统全栈实战:Java+JavaScript+CSS三层架构解析

📅 2026/9/13 15:34:44 | 华诺云谱 👁 阅读
宿舍报修系统全栈实战:Java+JavaScript+CSS三层架构解析
简介这是基于Java、JavaScript、CSS开发的宿舍报修系统完整设计源码面向高校后勤、物业管理部门及JavaWeb学习者用于实现学生在线报修、维修进度跟踪与后台管理一体化流程。资源共137个文件压缩包14.14MB包含36个class文件、34个JSP页面、18个java源文件、16张JPG图片、8个JavaScript脚本、8个JAR库、6个XML配置、4个CSS样式及项目配置文件等清晰覆盖后端业务逻辑、前端交互、页面展示与系统配置各层。已有114人学习下载。源码结构完整可直接导入Idea运行研究其中RepairController、StudentController、MangerController等类展示了报修、学生与管理端核心接口实现适合作为课程设计、毕业设计或中小型报修管理系统的参考范式能帮助读者快速理解前后端分离开发模式下的JSPServletJavaBean实际项目组织方式。1. 宿舍报修系统为什么适合做Java、JavaScript、CSS的组合靶场宿舍报修系统看起来是个小项目实际把Web开发的主线都占全了学生用表单提交报修单维修后台要能改状态列表需要异步刷新不同状态的视觉效果要区分。基于Java、JavaScript、CSS的宿舍报修系统设计源码正好把这三层分开Java负责接口和状态流转JavaScript负责页面交互CSS负责把状态变成可视化的颜色和布局。对新手来说这套组合比纯模板渲染的JSP项目更有价值因为能在一个项目里看到前端怎么调接口、后端怎么返回JSON对初级工程师来说它又是一个能在面试里讲清的完整闭环。下面按实体类、状态机、fetch交互和响应式样式一层层拆开讲。2. 用Java实现报修单的核心流转实体类、Service与接口2.1 用实体类把报修单的数据结构钉死先不管界面长什么样后端第一件事是定义RepairOrder。常见做法是让实体类和数据库表字段一一对应这样MyBatis或者JDBC的映射逻辑最省事。代码里我习惯用包装类型Integer而不是int因为数据库字段允许为null查询数据库时出现null赋给int会直接NPE。public class RepairOrder { private Integer id; private String dormNo; // 宿舍号例如 3-201 private String reporter; // 报修人姓名 private String phone; // 联系电话 private String description; // 故障描述 private Integer status; // 0待受理 1派单中 2维修中 3已完成 4已取消 private java.util.Date createTime; private java.util.Date updateTime; public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status status; } // 其余 getter/setter 略 }字段里最值得说明的是status。很多新手会用String字段存“待受理”这样的中文这会在后续判断时写出一堆字符串比较万一数据库里存了错别字查都查不出来。用Integer保存状态码再在展示层去翻译成中文是最稳妥的做法。createTime和updateTime可以直接交给数据库的CURRENT_TIMESTAMP维护Java侧不赋值。有些源码包会使用Lombok的Data能省掉getter/setter但如果你要拿到面试场景里讲还是建议至少手写关键字段的访问方法省得被问到时说不出原理。实体类本身不承担业务逻辑它只是一个数据容器真正决定状态走向的是Service层。2.2 Service层处理状态流转而不是把if写到Controller在宿舍报修系统里报修单不是随便什么状态都能跳过去的。待受理可以把单子派给维修工也可以取消维修中只能变成已完成已经完成的单子不能再改回维修中。这套规则如果散落在多个接口里后续维护非常容易漏判。我一般会用一个枚举把状态码和允许的跳转方向集中定义。public enum RepairStatus { PENDING(0), DISPATCHED(1), REPAIRING(2), DONE(3), CANCELED(4); private final int code; RepairStatus(int code) { this.code code; } public int getCode() { return code; } public static RepairStatus fromCode(int code) { for (RepairStatus s : values()) { if (s.code code) return s; } throw new IllegalArgumentException(未知的状态码: code); } public boolean canTransferTo(RepairStatus target) { switch (this) { case PENDING: return target DISPATCHED || target CANCELED; case DISPATCHED: return target REPAIRING; case REPAIRING: return target DONE; default: return false; } } }参数说明fromCode用来把数据库里读出来的int转成枚举避免到处写魔法数字。canTransferTo返回true代表允许迁移PENDING允许派单和取消两步其他状态都只允许单点前进。这样即使前端把按钮发错了后端仍然会拒绝非法流转。Service里的更新代码可以这样写public boolean updateStatus(Integer orderId, Integer targetCode) { RepairOrder order repairDao.findById(orderId); if (order null) { throw new RuntimeException(报修单不存在); } RepairStatus current RepairStatus.fromCode(order.getStatus()); RepairStatus target RepairStatus.fromCode(targetCode); if (!current.canTransferTo(target)) { throw new IllegalStateException(status is not transferable); } return repairDao.updateStatus(orderId, targetCode); }这段逻辑里有一个值得注意的点状态校验要放在权限校验之后否则修理工可以拿自己的token把别人的单子改成已完成。一般源码包会把角色判断塞在同一个Service方法里这里为了简化只保留了状态机。如果你要做权限可以在方法入口再加一个operatorRole参数用字符串区分。下面这张表是状态迁移的完整约束放在数据库设计文档里也很合适当前状态可跳转状态典型操作0 待受理1 派单中、4 已取消管理员派单或学生取消1 派单中2 维修中维修工开始维修2 维修中3 已完成维修工提交完成3 已完成无流程结束4 已取消无流程结束这里的约束如果放在数据库触发器里也能实现但会降低可调试性。放在Java枚举里单元测试可以覆盖每一种组合比翻触发器脚本快很多。2.3 用Servlet接口输出JSON给前端调用既然前端是JavaScript负责后端接口最好是输出JSON而不是直接渲染HTML。很多源码包用的是Servlet也有用Spring Boot的两者在前端调用方式上没有区别。下面是一个最简的Servlet写法适合内部学习和二次开发不需要引入额外框架。WebServlet(/api/repair/create) public class RepairCreateServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { req.setCharacterEncoding(utf-8); resp.setCharacterEncoding(utf-8); resp.setContentType(application/json); String dormNo req.getParameter(dormNo); String reporter req.getParameter(reporter); String phone req.getParameter(phone); String description req.getParameter(description); if (dormNo null || dormNo.trim().isEmpty() || description null || description.trim().isEmpty()) { resp.getWriter().write({\code\:1,\msg\:\宿舍号或故障描述不能为空\}); return; } RepairOrder order new RepairOrder(); order.setDormNo(dormNo.trim()); order.setReporter(reporter null ? 匿名 : reporter.trim()); order.setPhone(phone); order.setDescription(description.trim()); order.setStatus(0); RepairService service new RepairService(); boolean ok service.create(order); if (ok) { resp.getWriter().write({\code\:0,\msg\:\提交成功\}); } else { resp.getWriter().write({\code\:1,\msg\:\数据库写入失败\}); } } }代码逻辑说明doPost方法一开始设置请求和响应的编码这能解决绝大多数中文乱码问题。检查参数时用trim()去掉首尾空格否则 3-201和3-201会被当成两条记录。这里用application/json而不是text/html前端fetch照常也能处理。参数说明dormNo是宿舍号reporter是报修人phone不是必填项所以为空时直接存null。status设为0表示新单子默认待受理。业务逻辑全部委派给service.createServlet里不写JDBC操作这样后续切换到Spring依赖注入会很方便。如果面试或答辩时被问到JavaWeb和Spring Boot的区别你可以拿这个Servlet举例它本身就是一个轻量的HTTP入口核心职责是接收参数、调用Service、返回结果Spring Boot只是把装配对象这件事自动化了状态流转和数据库访问依然在Service和Mapper里。3. JavaScript负责的交互细节表单提交、列表渲染与防抖搜索3.1 用fetch把表单数据POST到后端页面交互的核心是fetch它比老项目里的XMLHttpRequest更直观也更容易和async/await配合。下面这段代码监听提交按钮把表单内容收集成一个对象再转成URL编码格式发送给Servlet。document.getElementById(submitBtn).addEventListener(click, async function () { const dormNo document.getElementById(dormNo).value.trim(); const description document.getElementById(desc).value.trim(); if (!dormNo || !description) { alert(宿舍号和故障描述不能为空); return; } const data { dormNo: dormNo, reporter: document.getElementById(reporter).value.trim(), phone: document.getElementById(phone).value.trim(), description: description }; try { const resp await fetch(/api/repair/create, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded;charsetUTF-8 }, body: new URLSearchParams(data) }); const result await resp.json(); if (result.code 0) { loadRepairList(); } else { alert(result.msg); } } catch (err) { console.error(请求失败, err); alert(网络异常请稍后再试); } });这段代码有几个容易被忽略的细节。new URLSearchParams(data)会把对象转成dormNo3-201description...的格式这样Servlet的getParameter不用改就能读到。fetch默认的Content-Type不是表单编码所以这里显式设置一下否则Servlet拿到的参数可能是null。try/catch的作用是防止接口返回非JSON时直接抛错比如404页面返回的HTML会让resp.json()失败。参数说明data对象的key必须和Servlet里req.getParameter(dormNo)完全一致差一个字符都会导致后端拿到null。手机号为空时Servlet会存null前端不需要额外处理。一个常见的坑是按钮没有指定type默认的submit会让整个页面刷新导致fetch代码执行完又立刻被form提交覆盖。解决方案是把按钮放在form外或者给form的submit事件绑定时禁止默认行为。3.2 渲染报修列表把状态码翻译成可见标签后端返回的JSON里通常是一个数组每个元素包含status整数前端需要把这个数字翻译成人能看懂的中文。用对象映射比if/else更简洁也更好扩展。function renderRepairList(list) { const container document.getElementById(repairList); if (!container) return; const statusText { 0: 待受理, 1: 派单中, 2: 维修中, 3: 已完成, 4: 已取消 }; container.innerHTML list.map(function (item) { const text statusText[item.status] || 未知状态; const className status-badge status- item.status; const descText item.description || ; return div classrepair-card span class className text /span h4 escapeHtml(item.dormNo) /h4 p escapeHtml(descText) /p /div; }).join(); } function escapeHtml(str) { return String(str) .replace(//g, amp;) .replace(//g, lt;) .replace(//g, gt;) .replace(//g, quot;); }说明map返回的是拼接好的HTML字符串最后用join()合并这样比逐行createElement更快。状态数字直接拼进class名例如status-1方便CSS按状态着色。escapeHtml函数用来转义用户输入的尖括号和引号防止有人把描述写成script标签这是一个很常见的安全漏洞。这里的状态映射和Java枚举里的code是对应关系如果后端改了状态码前端要同步修改否则会出现“未知状态”。比较稳妥的做法是让后端额外返回一个statusText字段前端直接显示但这会牺牲一部分模板的灵活性。对于宿舍报修这种小系统前端维护一份映射表就够用了。3.3 用防抖处理搜索输入框报修列表一般会加一个按宿舍号搜索的输入框。最直接的写法是监听input事件每次按键都调用loadRepairList但这会让每个字符都触发一次网络请求。这里要用到防抖这个javascript函数模式本质是做一个延时闭包把事件合并到最后一次触发。let searchTimer null; document.getElementById(searchInput).addEventListener(input, function (e) { clearTimeout(searchTimer); searchTimer setTimeout(function () { loadRepairList(e.target.value.trim()); }, 300); });参数说明300毫秒是常见的防抖窗口低于200毫秒可能会在快速输入时仍触发多次请求高于500毫秒会感觉搜索明显延迟。clearTimeout每次重置计时器所以只要用户还在连续输入函数就不会执行。e.target.value在回调执行时会保留当时的输入值不需要额外存储。注意这里的searchTimer是全局变量如果页面有多个输入框需要防抖最好用一个Map保存各自的timer id。下面这张表列出fetch和XMLHttpRequest的差异方便你判断为什么源码包普遍用fetch对比项fetchXMLHttpRequest语法Promise链式回调事件处理JSONresp.json()JSON.parse(responseText)取消请求AbortControllerxhr.abort()上传进度需要额外流读取onprogress事件如果你想支持IE浏览器那么fetch不能用只能退回到XHR。宿舍报修系统通常在内网使用浏览器版本往往不新这一点要在源码说明里写清楚。4. CSS决定源码的观感卡片布局、伪类选择器与交互动效4.1 用Flex布局实现卡片式列表报修列表最常见的展示方式是卡片。相比table卡片能装下更多信息块比如状态标签、宿舍号、描述文本和操作按钮。CSS里用Flex布局排卡片比float更简单也不会出现父元素高度塌陷的问题。.repair-list { display: flex; flex-wrap: wrap; gap: 16px; padding: 20px; justify-content: flex-start; } .repair-card { width: 280px; border: 1px solid #e5e5e5; border-radius: 8px; padding: 16px; background: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); transition: transform 0.15s ease, box-shadow 0.15s ease; } .repair-card:hover { transform: translateY(-3px); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); }说明gap是flex和grid都支持的间距属性不需要再给每个卡片单独设margin。flex-wrap: wrap让卡片在窄屏幕上自动换行。justify-content: flex-start保证最后一行卡片靠在左边不会因为一行只有两个就居中拉伸。hover上的translateY(-3px)是常见的鼠标移入事件反馈配合transition能得到很轻的浮起效果。这里要注意transition的耗时一般设0.15s到0.2s短了没感觉长了会显得拖泥带水。如果项目中已经有了动画库不要在这里叠加animation避免两个动效同时控制transform导致跳动。4.2 状态标签的伪类选择器与颜色语义状态标签是一个span用不同底色区分报修进度。CSS里可以直接通过类名控制颜色也可以用属性选择器。下面这段是类名方案清晰直观.status-badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; line-height: 20px; } .status-0 { color: #d48806; background: #fffbe6; } .status-1 { color: #1677ff; background: #e6f4ff; } .status-2 { color: #722ed1; background: #f9f0ff; } .status-3 { color: #389e0d; background: #f6ffed; } .status-4 { color: #8c8c8c; background: #fafafa; }说明状态“0”用黄色系表示等待“1”用蓝色表示派单“2”用紫色表示维修中“3”用绿色表示完成“4”用灰色表示取消。颜色语义和主流设计体系对齐用户不需要读文字也能快速感知。除了hoverCSS里还有几个在这类系统里常用的伪类选择器。表格如下伪类作用在报修系统中的使用场景:hover鼠标移入卡片浮起、按钮变色:focus获得焦点输入框高亮:disabled禁用状态已完成的单子禁用编辑按钮:nth-child第几个元素表格里给奇数行加底色如果描述中出现超时可以在HTML里加一个span classoverdue已超时/span然后.overdue { text-decoration: line-through; color: #bfbfbf; }删除线在这里不是单纯的装饰它明确告诉用户这条记录已经被系统标记为过期比加红字更克制也更容易和报错状态区分开。4.3 媒体查询让小屏设备不破版宿舍管理员经常用手机接收报修通知所以CSS必须处理窄屏。媒体查询是在不同宽度下覆盖默认样式而不是单独写一套页面。media (max-width: 640px) { .repair-list { padding: 12px; gap: 12px; } .repair-card { width: calc(100% - 24px); } .submit-form button { width: 100%; } }参数说明640px是常见的手机断点针对超过这个宽度的设备保持原样式。calc(100% - 24px)是为了减去父容器padding避免卡片宽度超出屏幕。按钮改成100%宽的按钮在手机上更容易点击热区面积也更大。这里有一个常见的误用在宽度选择器里重复设置已经在默认样式里写过的属性。媒体查询只需要覆盖需要变化的属性不需要把border-radius或background再抄一遍否则维护起来非常累。查看CSS源码时如果发现大量重复样式说明写代码的人没有利用层叠覆盖的特性。5. 数据库与接口联调用SQL和curl验证Java接口闭环5.1 用SQL初始化报修表和状态数据Java代码和前端逻辑再完整没有数据库支撑也跑不起来。报修系统最核心的表是repair_order下面是建表语句兼容MySQL 5.7及以上版本。CREATE TABLE repair_order ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, dorm_no VARCHAR(20) NOT NULL COMMENT 宿舍号, reporter VARCHAR(50) NOT NULL COMMENT 报修人, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, description VARCHAR(500) NOT NULL COMMENT 故障描述, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待受理 1派单中 2维修中 3已完成 4已取消, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宿舍报修单;说明status字段用TINYINT就够不会超过127。create_time默认CURRENT_TIMESTAMP插入时不用显式赋值。update_time加ON UPDATE每次更新记录时自动刷新省去Java侧手动set。表名用单数还是复数取决于团队规范这里用order而不是orders方便和Java实体类RepairOrder对应。KEY idx_status是普通索引主要给管理后台“按状态筛选”的查询加速。初始化数据可以写三条INSERT INTO repair_order (dorm_no, reporter, phone, description, status) VALUES (3-201, 张伟, 13800001111, 水龙头漏水, 0), (5-302, 李丽, 13800002222, 空调不制冷, 2), (6-101, 王强, 13800003333, 灯泡损坏, 3);这三条数据可以覆盖待受理、维修中、已完成三种状态前端渲染和上面的状态机都能直接验证。注意宿舍号用字符串而不是整数因为可能包含“-”或者“栋”字字符串类型对这类编码更合适。5.2 用curl验证接口时的常见用例写完代码不是直接打开网页点一下就算通过先用curl把接口调通能省掉浏览器里排查前端错误的时间。创建接口的验证命令是curl -X POST http://localhost:8080/api/repair/create \ -d dormNo3-201reporter张伟phone13800001111description水龙头漏水 \ -H Content-Type: application/x-www-form-urlencoded; charsetUTF-8预期返回{code:0,msg:提交成功}。如果返回code1说明参数校验或数据库写入出问题。查询接口可以带上status参数curl http://localhost:8080/api/repair/list?status0这里注意curl在终端里发送中文时某些Windows终端会使用GBK编码导致后端接收乱码。可以在请求里加--data-urlencode或者用文件保存请求体再-c但更简单的是先用英文字符串验证接口通路再把中文场景交给前端页面测。下面这张表是联调时的常用测试用例放在源码里的README上也合适场景请求方式期望结果正常提交POST返回code0数据库新增status0宿舍号为空POST返回code1和提示信息故障描述超长POST后端拒绝或数据库截断非法状态流转POST修改接口返回异常码数据库状态不变未登录访问GET返回401或跳转登录页5.3 用浏览器开发者工具定位404和乱码curl验证通过后再回到浏览器。如果页面报404先打开DevTools的Network标签找到那条红色的请求看请求URL和实际接口路径是否一致。常见原因是页面部署在/repair/这个二级路径下但fetch里写的/api/repair/create没有带上项目名。解决办法是用相对路径或者从页面标签里动态拼接contextPath不建议写死。中文乱码的定位顺序是先看Network里的Response Headers有没有Content-Type: application/json;charsetutf-8没有说明Servlet的resp.setCharacterEncoding没生效再看响应体里的中文是否已经乱码如果乱码则去检查数据库连接串是否加了characterEncodingutf8。前端显示正常但数据库里是乱码问题一般出在MySQL连接配置而不是Java代码。还有一个容易被忽略的点Servlet的WebServlet注解在某些源码包里会被web.xml覆盖掉导致你改了注解路径但访问的还是老地址。检查一下WEB-INF/web.xml里有没有相关的servlet-mapping如果有以web.xml配置为准。这个坑能卡住半天在源码排错时优先排查。6. 源码运行排错三个最影响体验的坑最后一个建议与其去翻整套源码不如先学会用浏览器验证前端和后端各自是否正常。下面三个坑是宿舍报修系统源码里最常见的每个都附一个定位思路。第一个页面加载后样式全丢了。打开Network标签看CSS请求的状态码是不是404。如果是检查HTML里引用的css路径hrefcss/style.css和href/css/style.css在部署路径不同时表现完全不同。前者是相对当前页面路径后者是从应用根路径开始两者差一个斜杠就访问不到。快速验证方法是在地址栏直接输入CSS的完整URL能打开说明路径没错打不开就是路径问题。第二个点击提交后没有反应。先把Console里的报错截图如果看到Unexpected token 说明fetch访问到了HTML页面而不是JSON接口通常是因为后端返回了404错误页。如果看到Cannot read properties of null说明DOM元素的id写错了检查document.getElementById的参数和页面里的id是否完全一致。第三个接口返回500。先看后端日志的堆栈常见原因是数据库驱动没打包到WEB-INF/lib下或者MySQL版本和驱动版本不兼容。用下面这段代码可以快速验证数据库连接是否正常try (Connection conn dataSource.getConnection()) { System.out.println(conn.isClosed()); } catch (SQLException e) { e.printStackTrace(); }把这段放到Servlet的init方法里启动后看控制台输出。如果输出false说明连接池正常。如果抛ClassNotFoundException就去检查驱动的jar包位置。排错不要凭感觉改代码每步都基于Network、Console和后端日志来定位。这三个面板一起看通常能在五分钟内找到问题源头。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。