ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java Web访客登记系统课程设计:从数据库到Servlet完整实现

Java Web访客登记系统课程设计:从数据库到Servlet完整实现 简介这是一份基于Java Web的公司访客登记系统课程设计完整资源包主要面向计算机专业学生及Java Web入门开发者用于完成访客信息管理类毕业设计或课程设计作业。系统实现了访客信息的添加、修改、删除、检索查询与分类统计五大核心功能可结合JSP、Servlet、JDBC等主流技术栈理解MVC三层架构并覆盖从页面交互到数据库操作的完整业务链路。压缩包共64个文件其中包括14个Java源文件、12个JSP页面、14个class文件以及SQL数据库脚本、系统报告文档、说明文件和工程配置文件总大小966KB资源结构清晰便于按源码、页面、数据库、报告分层查阅。目前已有127人学习下载适合需要参考完整项目代码、数据库设计或实训报告写法的读者。通过研读源码与SQL脚本可以快速掌握企业访客登记场景下的数据表设计、条件查询、分类统计及前后端交互流程为独立完成同类课程设计提供有效支撑。1. 课程设计题目拆解访客登记系统考的就是这三件事前台放一本纸质访客登记本来访人自行填姓名电话签离时再划一笔这套流程在很多公司运行了十几年。课程设计把它搬上 Java web要交付的无非三件套能跑的源码、能导入的数据库脚本、能讲清楚的设计报告。别小看这个题目它把 Java web 入门阶段最重要的东西全部覆盖了页面到 Servlet 的请求流转、JDBC 对 MySQL 的增删改查、以及把登记数据变成统计报表的聚合 SQL。适合正在选题的学生也适合刚学完 Java 基础想拿一个完整 web 项目练手的开发者。常见做法是把系统拆成前台登记端和管理员查询端登记端负责来访登记、签离、预约管理员端负责查询记录、统计报表、导出数据。这个拆分不是功能拍脑袋而是对应数据库里多个入口访问同一张表用状态字段区分业务阶段的设计思路评卷老师通常盯的就是这张表设计。2. 技术选型与数据库建模访客系统先定 ER 图再写业务代码好多同学拿到题目第一件事是新建 Maven 项目这顺序反了。课程设计的评分逻辑和公司项目不一样老师会先翻报告里的数据库设计再跑系统验证功能。所以先把表定下来再回头写代码反而能少返工。2.1 选型JSPServletJDBC 为什么比 Spring Boot 更稳这个题目的技术栈有三个常见选择纯 JSPServletJDBC、JSPServletMyBatis、Spring Boot Thymeleaf。我一般推荐第一种原因是课程设计报告要求把请求处理流程写清楚Servlet 里从取参、校验、调 DAO 到转发页面都是显式的每个方法都能对应到报告的一小节。Spring Boot 当然也可以但若课程大纲本身没讲 Spring答辩时被问到为什么用框架容易答偏。技术选型表组件版本建议负责的事JDK8 或 11编译运行环境Tomcat9.x配 JDK8Servlet 容器承载 web 应用MySQL5.7 或 8.0持久化访客数据连接驱动mysql-connector-java 5.1.49 或 8.0.xJDBC 桥接前端JSP jQuery ECharts页面渲染与统计图这里有一个常见翻车点Tomcat 10 把javax.servlet包改成了jakarta.servlet如果课设代码是从旧项目抄的import javax.servlet.http.HttpServlet部署到 Tomcat 10 会直接编译报错。保守起见要么配 Tomcat 9 JDK 8要么把依赖换成jakarta.servlet-api。数据库驱动版本同理MySQL 8 的驱动类名是com.mysql.cj.jdbc.Driver而 5.x 用com.mysql.jdbc.Driver两者不通用。2.2 访客与用户表设计一个状态字段串起登记、在访、签离、预约表结构是这份课设报告的灵魂。常见的合理设计是两张表t_user存前台和管理员账号t_visitor存访客来访记录。很多同学会把预约单独建一张预约表再和来访记录关联对于课程设计这个体量没必要额外引入联表查询反而增加出错面。用单一status字段表达业务阶段既是主流做法统计报表时也少写一次 JOIN。CREATE DATABASE visitor_sys DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE visitor_sys; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT MD5 后的密码, real_name VARCHAR(50) NOT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 1 前台2 管理员, dept VARCHAR(50) COMMENT 所属部门 ) ENGINEInnoDB; CREATE TABLE t_visitor ( id INT PRIMARY KEY AUTO_INCREMENT, visitor_name VARCHAR(50) NOT NULL COMMENT 访客姓名, phone VARCHAR(20) NOT NULL, company VARCHAR(100) COMMENT 来访单位, visit_target VARCHAR(50) NOT NULL COMMENT 被访人, visit_reason VARCHAR(200) COMMENT 来访事由, status TINYINT NOT NULL DEFAULT 0 COMMENT 0 在访1 签离2 预约3 取消, arrive_time DATETIME COMMENT 签到时间, leave_time DATETIME COMMENT 签离时间, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB; CREATE INDEX idx_visitor_status ON t_visitor(status); CREATE INDEX idx_visitor_arrive ON t_visitor(arrive_time);说明几个设计要点。status用 TINYINT 而不是字符串是为了让后续 SQL 的WHERE status 0走索引更高效也便于报表分组arrive_time和create_time分开是因为预约场景下记录创建时间和实际到访时间不是一回事。两个索引是给报表查询准备的GROUP BY DATE(arrive_time)和频繁的WHERE status ?都用得到。ER 图画这张图就够了t_user与t_visitor在业务上通过前台操作记录关联图上画成一对多即可。2.3 初始化数据与 JDBC 连接参数五分钟跑通数据库数据库建好后要初始化一个管理员账号密码建议先 MD5 存进去避免明文写在 SQL 里-- 初始化账号密码为 admin123 的 MD5 值 INSERT INTO t_user(username, password, real_name, role) VALUES(admin, 0192023a7bbd73250516f069df18b500, 系统管理员, 2);JDBC 连接参数是另一个高频报错点。写一个DBUtil工具类统一管理连接URL 里显式指定时区能省掉大量时间差 8 小时的排查时间public class DBUtil { // MySQL 8 必须带 serverTimezone否则连接报时区错误 private static final String URL jdbc:mysql://localhost:3306/visitor_sys?useUnicodetrue characterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() throws SQLException { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new SQLException(驱动类未找到请检查 mysql-connector-java 版本, e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }URL 里的三个参数分别对应三件常踩的事useUnicodetrue配合characterEncodingutf8保证中文不乱码serverTimezone指定数据库所在时区不写的话 MySQL 8 驱动会把它当作与本地时间相同而给出错误偏移签离时间可能比签到时间还早半天数据库端和连接端的COLLATE保持一致避免联表排序时出现字符集不匹配。用户名密码不要写死在代码里交作业报告里注明这是演示配置即可。3. 核心业务实现登记、签离、预约的 Java 代码怎么写才不被问倒数据库定好后代码其实只剩下三件事登记时插入一条在访记录签离时把同一行改成已签离预约时先生成预约状态再等人确认。把这三段写清楚这个项目的核心就完成了。3.1 登记接口插入之前先做非空和手机号校验登记页放在register.jsp提交到VisitorRegisterServlet。Servlet 里先做参数校验再写库是答辩时最值得讲的一段——很多同学的代码是前端校验完就直接插数据库实际上前端校验可以被绕过后端必须重复一遍。WebServlet(/visitor/register) public class VisitorRegisterServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); resp.setContentType(application/json;charsetUTF-8); String name trim(req.getParameter(visitorName)); String phone trim(req.getParameter(phone)); String company trim(req.getParameter(company)); if (name.isEmpty()) { resp.getWriter().write({\code\:400,\msg\:\访客姓名不能为空\}); return; } // 只做手机号长度和首位的格式判断不校验真伪 if (phone.length() ! 11 || !phone.startsWith(1)) { resp.getWriter().write({\code\:400,\msg\:\手机号格式不正确\}); return; } try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement( INSERT INTO t_visitor (visitor_name, phone, company, visit_target, visit_reason, status, arrive_time) VALUES(?,?,?,?,?,0,NOW()))) { ps.setString(1, name); ps.setString(2, phone); ps.setString(3, company); ps.setString(4, trim(req.getParameter(visitTarget))); ps.setString(5, trim(req.getParameter(visitReason))); int rows ps.executeUpdate(); if (rows 0) { resp.getWriter().write({\code\:200,\msg\:\登记成功\}); } } catch (SQLException e) { e.printStackTrace(); resp.getWriter().write({\code\:500,\msg\:\数据库异常\}); } } }这段代码有四个可以主动讲的点为什么不拼接 SQL 而用PreparedStatement因为参数化查询能挡住 SQL 注入这是课程设计报告里必须写的一条status直接在 INSERT 里写死 0而不是先查一次再插入避免多一次查询NOW()由数据库生成时间保证和报表里的DATE(arrive_time)使用同一时钟源返回 JSON 而不是转发 JSP让登记页用 jQuery 的$.post提交后根据 code 决定提示还是跳转页面不用整体刷新。phone 的校验只做了长度和首位接口文档里要注明不做运营商真实性校验这属于演示系统的合理边界。3.2 签离接口更新同一行而不是插入新记录签离的错误实现是把一条新记录插入表里并把旧记录置为已签离这会产生两条源访客数据报表统计会翻倍。正确的做法是对同一个id做行内更新把status从 0 改成 1同时写入leave_timeWebServlet(/visitor/signOut) public class VisitorSignOutServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int id Integer.parseInt(req.getParameter(id)); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement( UPDATE t_visitor SET status 1, leave_time NOW() WHERE id ? AND status 0)) { ps.setInt(1, id); int rows ps.executeUpdate(); if (rows 1) { resp.getWriter().write({\code\:200}); } else { // rows 为 0 说明记录不存在或已经签离过 resp.getWriter().write( {\code\:400,\msg\:\记录不存在或已签离\}); } } catch (SQLException e) { resp.getWriter().write({\code\:500,\msg\:\数据库异常\}); } } }关键在WHERE id ? AND status 0。这个条件让更新天然具备幂等性前端连点两次签离按钮第一次把 status 改成 1 并写入 leave_time第二次WHERE匹配不到任何行rows是 0 而不是抛异常。这样既避免了重复签离又不需要额外加一张防重表。答辩时老师常问两个入口同时签离同一个人会怎样回答这个条件就是答案。3.3 预约与确认用一张表里的 status 走完预约流程预约场景下访客先填预约表单数据插入时status 2、arrive_time存预计到访时间前台在预约列表里点击确认时把status从 2 改成 0并把arrive_time更新为当前实际时间如果爽约则改成 3。整个过程不新增表只在状态机上迁移String sql UPDATE t_visitor SET status ?, arrive_time ? WHERE id ?; // 确认 参数为 (0, 当前时间, id) // 取消 参数为 (3, 原值, id) // 这个接口不接受 status 1签离只能由签离接口完成这里需要说明为什么确认时改arrive_time预约时存的是预计到达时间确认到访时应该把实际签到时间写进去否则日报统计会把预约生成时间当成到访时间统计口径就乱了。状态机的四个取值在前端下拉框里展示成中文文案时用一个 Map 或 switch 转换即可不要在前端写死数字。业务规则汇总如下前端动作status 变化arrive_timeleave_time现场登记无 - 0当前时间空提交预约无 - 2预计时间空确认预约到访2 - 0覆盖为实际时间空取消预约2 - 3不变空签离0 - 1不变当前时间把这五条规则做成一张表写进报告比写十页代码更能说明你理解了这个业务。实现层面再补一个约束确认和取消按钮在 JSP 列表里要带status 2的判断避免状态错乱。4. 分页查询与统计报表把访客数据变成能进报告的结果登记和签离是数据入口报表才是决定这门课设上限的部分。老师跑完系统后会重点看查询页能不能筛条件、报表数字对不对这两块正好对应数据库课里学的 WHERE 组合和 GROUP BY。4.1 多条件查询 DAO参数拼接与分页的配合访客列表页要支持按被访人、来访日期范围、状态三个条件组合筛选。DAO 层用动态拼接 WHERE 的方式实现注意每加一个条件都要对应一个?参数按顺序 setpublic ListVisitor query(String target, String startDate, String endDate, Integer status, int page, int pageSize) { StringBuilder sql new StringBuilder( SELECT id, visitor_name, phone, company, visit_target, status, arrive_time, leave_time FROM t_visitor WHERE 11); ListObject params new ArrayList(); if (target ! null !target.isEmpty()) { sql.append( AND visit_target LIKE ?); params.add(% target %); } if (startDate ! null !startDate.isEmpty()) { sql.append( AND DATE(arrive_time) ?); params.add(startDate); } if (endDate ! null !endDate.isEmpty()) { sql.append( AND DATE(arrive_time) ?); params.add(endDate); } if (status ! null) { sql.append( AND status ?); params.add(status); } sql.append( ORDER BY arrive_time DESC LIMIT ?, ?); params.add((page - 1) * pageSize); params.add(pageSize); // 执行 PreparedStatement按 params 顺序 setObject 即可 }WHERE 11不是多余的写法它让后续每个AND分支都不用判断是不是第一个条件代码可读性更好执行计划也不受影响。分页用LIMIT ?, ?第一个参数是偏移量(page - 1) * pageSize第二个是每页条数记得 set 的时候两个都得转成 int因为 MySQL 的 LIMIT 不接受字符串参数。日期比较用DATE(arrive_time) ?而不是arrive_time ?这样传2024-05-01也能匹配当天的任意时刻记录不会被隐式类型转换坑到。4.2 日报统计 SQL按天分组后注入 ECharts 数据报表页最常见的图表是最近 7 天每日来访人次对应 SQL 是SELECT DATE(arrive_time) AS visit_date, COUNT(*) AS total FROM t_visitor WHERE arrive_time DATE_SUB(CURDATE(), INTERVAL 6 DAY) AND status IN (0, 1) GROUP BY DATE(arrive_time) ORDER BY visit_date;status IN (0, 1)把取消的预约排除在统计口径外COUNT(*)统计的是来访人次而不是人数同一个访客来两次算两笔报告里要写明这个口径。Servlet 里把结果集包装成ListMapString, Object用 Jackson 或手拼字符串转成 JSON 返回前端 ECharts 拿date和total两个数组直接画柱状图。日期缺失的某一天返回值为空前端要补 0而不是让图出现空洞。4.3 CSV 导出报告附录里的数据从哪来课程设计报告要求放运行截图和数据说明与其截图一堆列表页不如做一个导出 CSV 的接口把筛选条件下的数据原样导出用 Excel 打开后可以直接贴进报告附录resp.setContentType(text/csv;charsetUTF-8); resp.setHeader(Content-Disposition, attachment;filenamevisitor_ System.currentTimeMillis() .csv); resp.getWriter().write(\uFEFF); // BOM否则 Excel 打开中文会乱码 resp.getWriter().write(姓名,手机号,来访单位,被访人,状态,签到时间,签离时间\n); while (rs.next()) { resp.getWriter().write(rs.getString(visitor_name) , rs.getString(phone) , rs.getString(company) , rs.getString(visit_target) , statusText(rs.getInt(status)) , rs.getString(arrive_time) , rs.getString(leave_time) \n); }\uFEFF是 UTF-8 BOM不加它 Excel 默认按 ANSI 编码读取中文全部乱码这是导出功能最常见的坑。字段里如果允许逗号或换行符导出前要做转义课程设计里可以在登记校验阶段就禁止特殊字符把问题挡在源头。导出接口不需要单独的表直接把 4.1 那个动态查询条件复用到导出 SQL 上即可。5. 答辩演示检查清单从跑通到能讲清楚的最小验收系统能跑只是及格线答辩环节老师更想听到你怎么解释设计决策。演示顺序要有剧本先给结论这个题目最值得讲的就是状态机设计和报表口径把这两个讲透20 分钟答辩时间足够。5.1 演示路径与话术按登记 - 列表 - 签离 - 报表 - 导出的顺序操作每步配一句解释。登记时强调后端二次校验和 PreparedStatement 防注入签离时强调WHERE status 0的幂等性报表时强调统计口径排除了取消状态。前台账号进系统只能看到登记页和签离列表管理员账号才能看统计报表和导出按钮这个权限差别用 Filter 实现比在 JSP 里 if 判断更值得讲。WebFilter(/*) public class LoginFilter implements Filter { private static final SetString WHITE_LIST Set.of(/login.jsp, /loginServlet); 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(false); if (WHITE_LIST.contains(request.getRequestURI()) || (session ! null session.getAttribute(user) ! null)) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }演示前把浏览器缓存清空开启控制台 Network 面板这样每次请求的 200/302 状态码都能被老师看到比口头说跳转了更有说服力。时间字段全部用数据库的 NOW() 生成演示时的签到时间一定和系统当前时间一致不会出现签离时间早于签到时间这种损害信任度的低级错误。5.2 三个最容易暴露的坑演示前必须自查检查点自查方法修复方式中文乱码登记一个含中文的单位名JSP 顶部声明 pageEncodingServlet 里 req.setCharacterEncoding重复点击签离按钮快速点两下WHERE 条件带 status0观察第二次返回值时间偏移看签到和签离时间差值JDBC URL 加 serverTimezoneAsia/Shanghai最后一个技巧把数据库初始化 SQL 和账号密码写进报告附录评卷老师导入数据库时能一次成功这份印象分值得花五分钟。源码目录按src、web、sql、docs四层整理报告里的 ER 图、用例图、表结构说明和源码注释保持一致的命名这套交付节奏不仅适用于访客登记系统也是后续所有课程设计的通用模板。本文还有配套的精品资源点击获取
返回列表