FEATURED · 精选文章

JSP+Servlet抽奖系统:Web开发五层核心逻辑实战

发布时间 / 2026/9/17 17:19:45
来源 / 创域科博编辑部
栏目 / 资讯中心
JSP+Servlet抽奖系统:Web开发五层核心逻辑实战 简介本资源是一份完整的本科毕业设计论文面向计算机专业学生及Web开发初学者聚焦JSP技术在企业级抽奖系统中的工程化落地。论文围绕B/S架构设计涵盖个人中心、抽奖核心、留言交互三大模块结合Microsoft SQL Server数据库与JSP/Servlet技术栈系统阐述需求分析、系统设计、安全机制如身份验证、SQL注入防护及UI友好性实现适用于客户关系管理与营销活动场景。资源为单文件.docx格式大小949KB内容完整包含摘要、目录、系统架构图、功能模块说明、关键技术分析JSP动态页面生成、会话管理、数据库交互及10篇中英文参考文献结构规范、排版清晰。已有104人学习下载可直接用于毕业设计参考、课程设计复用或JSP项目实践入门尤其适合需快速掌握Web系统设计全流程的学习者。1. 为什么一个“过时”的JSP抽奖系统至今仍是Web开发入门的硬核练兵场你可能在招聘JD里见过“熟悉Java Web基础”——但真让你手写一个能跑通的、带数据库交互、有权限隔离、能防基础越权的完整B/S流程很多人会卡在request.getParameter()之后。这个基于JSP的抽奖系统表面看是2010年代的技术栈JSP Servlet SQL Server 2008 MyEclipse但它恰恰把Web开发最底层、最不可跳过的五层逻辑——HTTP请求生命周期、会话状态管理、SQL注入防御边界、MVC职责切分、B/S权限校验链路——全压在一个轻量级项目里。它不追求高并发或微服务而是用最直白的% %脚本、最原始的HttpSession.getAttribute(userRole)、最朴素的PreparedStatement参数化查询把“用户点击抽奖按钮 → 后端生成随机数 → 检查报名资格 → 写入中奖记录 → 前端刷新结果”这条主干道一砖一瓦铺给你看。对新手它是照着能抄、改着能跑的实体教具对5年工程师它是一面镜子当你用Spring Security重写登录拦截器时回看当年login.jsp里那个if (session null || session.getAttribute(username) null)的手动校验会更清楚框架到底帮你省掉了什么、又隐藏了哪些陷阱。2. JSPServlet的MVC骨架如何落地从页面跳转到业务逻辑的精准路由2.1 JSP不是HTML它的动态能力必须由Servlet驱动很多初学者误以为JSP能独立完成所有逻辑实际在该系统中所有关键操作如用户注册、抽奖执行、留言提交均由Servlet处理JSP仅负责渲染。以抽奖核心流程为例用户点击draw.jsp中的“开始抽奖”按钮表单form actionDrawServlet methodpost将请求发往DrawServletDrawServlet继承HttpServlet在doPost()中获取request.getParameter(userId)调用DrawService.draw(userId)执行业务Service层通过PreparedStatement执行SQL“SELECT COUNT(*) FROM lottery_apply WHERE user_id ? AND status applied”验证用户是否已报名若通过则生成随机数并更新中奖状态“UPDATE lottery_apply SET status win, draw_time GETDATE() WHERE id ?”最后request.setAttribute(result, 恭喜中奖)request.getRequestDispatcher(draw_result.jsp).forward(request, response)跳转至结果页。提示此处forward()是服务器端跳转URL不变且request对象可传递数据若用response.sendRedirect()则是客户端重定向URL变化且request属性丢失——这是JSP开发中最易混淆的两种跳转方式。2.2 三层结构拆解JSP/Servlet/DAO的职责铁律该系统虽未使用现代框架但严格遵循分层原则各层代码位置与职责明确层级目录位置核心职责关键技术点ViewJSP/WebContent/渲染HTML嵌入% %输出变量用c:if等JSTL标签控制显示逻辑request.getAttribute(userList)获取数据c:forEach遍历列表ControllerServlet/src/com/lottery/servlet/接收HTTP请求调用Service处理跳转WebServlet(/LoginServlet)注解声明URL映射HttpSession管理登录态ModelDAO/Service/src/com/lottery/dao//src/com/lottery/service/封装数据库操作DAO和业务规则ServiceConnection conn DBUtil.getConnection()获取连接PreparedStatement防SQL注入2.2.1 DAO层的关键安全实践为什么不用Statement原文档第1.2.1节提到“JDBC连接数据库”但未说明具体实现。实际代码中所有数据库操作均使用PreparedStatement而非Statement// ✅ 正确参数化查询杜绝SQL注入 String sql INSERT INTO users (username, password, email) VALUES (?, ?, ?); PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, username); // 自动转义单引号 pstmt.setString(2, password); pstmt.setString(3, email); pstmt.executeUpdate(); // ❌ 危险字符串拼接极易被注入 String sqlBad INSERT INTO users VALUES ( username , password , email ); Statement stmt conn.createStatement(); stmt.executeUpdate(sqlBad); // 若usernameadmin--则密码被绕过注意PreparedStatement的setString()方法会对特殊字符如单引号、分号自动转义这是JSP时代对抗SQL注入的基石。即使系统用SQL Server 2008也必须坚持此写法——因为漏洞不在数据库版本而在代码逻辑。2.3 B/S权限校验链从URL直连到Session失效的完整防护系统区分用户与管理员但权限控制并非仅靠前端隐藏按钮c:if test${user.role admin}而是构建了三层校验URL访问拦截AdminServlet开头强制检查session.getAttribute(role)是否为admin否则重定向至error.jsp数据库查询隔离管理员查看用户列表时SQL语句为SELECT * FROM users WHERE role ! admin避免普通用户数据泄露操作级越权防护用户修改个人信息时UpdateUserServlet中校验request.getParameter(id)是否等于session.getAttribute(userId)防止通过修改URL参数篡改他人信息。2.3.1 Session失效的典型场景与调试技巧当用户长时间无操作后session.getAttribute(username)返回null但系统未做空指针防护。修复方案需在每个Servlet的doGet/doPost开头添加HttpSession session request.getSession(false); // false表示不创建新session if (session null || session.getAttribute(username) null) { response.sendRedirect(login.jsp?msglogin_required); // 重定向带提示 return; // 立即终止后续执行 }提示getSession(false)比getSession(true)更安全避免无意义的session创建重定向时通过URL参数?msg传递错误信息再在login.jsp中用% request.getParameter(msg) %显示比弹窗更符合B/S无状态特性。3. SQL Server 2008的实战配置从安装到JDBC连接的避坑指南3.1 数据库设计的核心矛盾关系完整性 vs 抽奖业务灵活性该系统ER图文档3.3节包含users用户、lottery_apply报名、lottery_win中奖三张主表其外键约束体现经典设计思想lottery_apply.user_id→users.id确保报名者必须是真实注册用户lottery_win.apply_id→lottery_apply.id中奖记录必须关联有效报名但lottery_apply.status字段采用字符串枚举applied/win/lose而非外键关联status_dict表——这是为简化而做的合理妥协避免小系统过度设计。3.1.1 关键SQL脚本建表与初始化数据-- 创建用户表含密码加密存储提示 CREATE TABLE users ( id INT IDENTITY(1,1) PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 实际应存BCRYPT哈希值非明文 email VARCHAR(100), role VARCHAR(20) DEFAULT user, -- user or admin create_time DATETIME DEFAULT GETDATE() ); -- 创建报名表含复合唯一索引防重复报名 CREATE TABLE lottery_apply ( id INT IDENTITY(1,1) PRIMARY KEY, user_id INT NOT NULL, apply_time DATETIME DEFAULT GETDATE(), status VARCHAR(20) DEFAULT applied, -- applied,win,lose CONSTRAINT FK_user_id FOREIGN KEY (user_id) REFERENCES users(id), CONSTRAINT UQ_user_status UNIQUE (user_id, status) -- 同一用户同一状态只允许一条 ); -- 初始化管理员账号密码为admin123的MD5哈希实际项目需用更强算法 INSERT INTO users (username, password, role) VALUES (admin, 202cb962ac59075b964b07152d234b70, admin);注意UQ_user_status唯一索引确保用户不能重复报名这是抽奖公平性的数据库层保障password字段虽存MD5但文档摘要强调“安全性更高”实则需提醒读者MD5已不安全生产环境必须升级为BCRYPT或PBKDF2。3.2 JDBC连接SQL Server的四大致命错误及修复MyEclipse中配置JDBC时90%的失败源于以下配置项错误错误类型典型表现正确配置SQL Server 2008 R2驱动类名错误ClassNotFoundException: com.microsoft.jdbc.sqlserver.SQLServerDriver使用新版驱动com.microsoft.sqlserver.jdbc.SQLServerDriver注意包路径URL格式错误No suitable driver found for jdbc:microsoft:sqlserver://...标准URLjdbc:sqlserver://localhost:1433;databaseNameLotteryDB;usersa;password123456身份验证失败Login failed for user sa在SQL Server Management Studio中右键服务器→属性→安全性→选“SQL Server和Windows身份验证模式”重启服务启用sa账户并设密码TCP/IP未启用Connection refused: connect运行SQL Server Configuration Manager→SQL Server网络配置→MSSQLSERVER协议→启用TCP/IP→重启SQL Server服务3.2.1DBUtil.java工具类的健壮性增强原文档未提供数据库工具类但实际项目必须封装连接池。以下是精简可用的DBUtilpublic class DBUtil { private static final String URL jdbc:sqlserver://localhost:1433;databaseNameLotteryDB; private static final String USER sa; private static final String PASSWORD 123456; static { try { Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); } catch (ClassNotFoundException e) { throw new RuntimeException(SQL Server Driver not found!, e); } } public static Connection getConnection() throws SQLException { // 生产环境应替换为HikariCP等连接池 return DriverManager.getConnection(URL, USER, PASSWORD); } // 关闭资源的标准写法Java 7 try-with-resources public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception e) { e.printStackTrace(); // 记录日志而非抛出 } } } } }提示Class.forName()在JDBC 4.0已非必需但显式调用可提前暴露驱动缺失问题close()方法接受可变参数支持一次关闭Connection、PreparedStatement、ResultSet避免资源泄漏。4. 抽奖算法的工程实现伪随机数的安全边界与业务校验4.1 为什么Math.random()不能直接用于抽奖文档未说明抽奖算法细节但根据功能描述“筛选出中奖名单”系统必然存在随机选择逻辑。常见错误是直接用Math.random()// ❌ 危险Math.random()生成[0,1)浮点数转int易产生偏差 int randomId (int)(Math.random() * userList.size()); // ✅ 正确使用SecureRandom保证密码学强度虽抽奖非金融场景但养成习惯 SecureRandom secureRandom new SecureRandom(); int randomIndex secureRandom.nextInt(userList.size()); User winner userList.get(randomIndex);注意Math.random()基于线性同余生成器可预测SecureRandom使用操作系统熵源适合需要不可预测性的场景。对于企业抽奖即使非金融级也应优先选用SecureRandom。4.2 业务层双重校验技术随机性与规则合规性真正的抽奖逻辑绝不仅是“取随机数”而是多层过滤后的结果资格过滤SELECT id FROM lottery_apply WHERE status applied AND user_id IN (SELECT id FROM users WHERE status active)去重保障若抽中已中奖用户自动跳过并抽取下一个避免while (winner.isWin())无限循环需设最大尝试次数数量控制管理员设置本次抽奖名额mSQL中用TOP m限制-- SQL Server语法随机取m个中奖者ORDER BY NEWID()实现随机排序 SELECT TOP ? id FROM lottery_apply WHERE status applied ORDER BY NEWID()4.2.1NEWID()vsORDER BY RAND()的性能真相SQL Server中ORDER BY NEWID()是公认高效的随机抽样方案因其为每行生成唯一GUID并排序而ORDER BY RAND()在SQL Server中不被支持MySQL才支持。测试表明对10万行数据NEWID()耗时约120ms远优于子查询SELECT * FROM table WHERE id IN (SELECT TOP 10 id FROM table ORDER BY NEWID())的嵌套开销。4.3 防刷机制同一IP/用户的抽奖频率控制文档未提及防刷但生产环境必须添加。在DrawServlet中增加// 获取用户IP考虑代理情况 String ip request.getHeader(X-Forwarded-For); if (ip null || ip.isEmpty()) { ip request.getRemoteAddr(); } // 查询该IP近1小时内抽奖次数 String ipCheckSql SELECT COUNT(*) FROM lottery_log WHERE ip ? AND draw_time DATEADD(HOUR, -1, GETDATE()); PreparedStatement ipPstmt conn.prepareStatement(ipCheckSql); ipPstmt.setString(1, ip); ResultSet rs ipPstmt.executeQuery(); if (rs.next() rs.getInt(1) 3) { // 限3次/小时 request.setAttribute(error, 操作过于频繁请1小时后再试); request.getRequestDispatcher(draw.jsp).forward(request, response); return; } // 记录抽奖日志 String logSql INSERT INTO lottery_log (user_id, ip, draw_time) VALUES (?, ?, GETDATE()); PreparedStatement logPstmt conn.prepareStatement(logSql); logPstmt.setInt(1, userId); logPstmt.setString(2, ip); logPstmt.executeUpdate();提示X-Forwarded-For头用于获取真实IP但需信任反向代理DATEADD(HOUR, -1, GETDATE())是SQL Server时间计算函数比Java端计算更可靠避免时区差异。5. MyEclipse Tomcat的联调排错从404到500的逐层定位法5.1 404错误的黄金排查顺序URL映射、目录结构、部署路径当访问http://localhost:8080/Lottery/draw.jsp报404按此顺序检查确认项目部署路径MyEclipse中右键项目→Properties→MyEclipse→Web→Context root确保为Lottery非默认/验证JSP物理路径/WebContent/draw.jsp是否存在且文件编码为UTF-8中文乱码常被误判为404检查Servlet映射web.xml中servlet-mapping的url-pattern是否为/DrawServlet且与表单action完全一致区分大小写Tomcat工作目录进入Tomcat/work/Catalina/localhost/Lottery/查看是否有draw_jsp.java编译文件——若无说明JSP未被Tomcat识别需检查web.xml中jsp-config是否禁用JSP。5.1.1web.xml的最小必要配置该系统web.xml必须包含以下核心节点?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 首页欢迎文件 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list !-- Servlet定义若用注解可省略 -- servlet servlet-nameLoginServlet/servlet-name servlet-classcom.lottery.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/LoginServlet/url-pattern /servlet-mapping !-- JSP编码过滤器解决中文乱码 -- filter filter-nameCharacterEncodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping /web-app注意version4.0对应Servlet 4.0规范支持WebServlet注解若用注解servlet和servlet-mapping可删除但welcome-file-list和filter仍需保留。5.2 500错误的根因分析从堆栈日志到SQL执行计划当DrawServlet报500第一步必看Tomcat日志catalina.out定位异常类如NullPointerExceptionlocalhost.date.log查看具体哪行代码抛出如DrawService.java:45数据库日志若报SQLException开启SQL Server Profiler跟踪SQL:BatchCompleted事件确认SQL是否执行、参数是否正确。5.2.1 常见500场景与修复代码异常现象日志关键词修复方案NullPointerExceptionat com.lottery.servlet.DrawServlet.doPost(DrawServlet.java:32)检查request.getParameter(userId)是否为null添加if (userId nullSQLException: Cannot open databaseLogin failed for user sa检查DBUtil.java中数据库名、用户名、密码是否与SQL Server实例匹配ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriverjava.lang.ClassNotFoundException将sqljdbc4.jarSQL Server 2008驱动复制到WebContent/WEB-INF/lib/并在MyEclipse中右键→Build Path→Add to Build Path提示sqljdbc4.jar必须与SQL Server版本严格匹配——2008 R2用sqljdbc4.jar2012用sqljdbc41.jar或sqljdbc42.jar复制后需重启Tomcat否则类加载器不会扫描新jar。5.3 JSP页面调试的终极技巧查看编译后的Java源码MyEclipse中JSP最终被Tomcat编译为Java文件路径为Tomcat/work/Catalina/localhost/Lottery/org/apache/jsp/。例如draw.jsp编译为draw_jsp.java打开后可见// JSP中% username %被编译为 out.print((java.lang.String)pageContext.findAttribute(username)); // JSP中c:forEach items${list} varitem被编译为 java.util.Iterator _jspx_iter_list ((java.util.List)pageContext.findAttribute(list)).iterator();注意通过阅读编译文件可清晰理解JSP表达式、JSTL标签的底层执行逻辑这是调试EL表达式不生效或JSTL标签不解析问题的最直接手段——若draw_jsp.java中无对应out.print()语句说明JSP未被正确识别检查web.xml中jsp-config或Tomcat版本兼容性。6. 从毕业设计到生产环境三个必须升级的安全加固点6.1 密码存储从明文/MD5到BCRYPT的强制迁移文档摘要称“系统安全性能更高”但users.password字段存的是MD5如202cb962ac59075b964b07152d234b70对应123这在2024年已属严重风险。升级方案引入BCrypt依赖pom.xmldependency groupIdorg.springframework.security/groupId artifactIdspring-security-crypto/artifactId version5.8.5/version /dependency注册时密码加密String rawPassword request.getParameter(password); String encodedPassword new BCryptPasswordEncoder().encode(rawPassword); // 存入数据库的encodedPassword长度为60位如$2a$10$...登录时验证BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); if (encoder.matches(inputPassword, dbPassword)) { // matches自动处理盐值 // 登录成功 }提示BCRYPT的$2a$前缀表示算法版本10为强度因子log2迭代次数值越大越安全但越慢生产环境建议12。6.2 XSS防护JSP输出的三重过滤策略用户留言、用户名等动态内容若直接% request.getAttribute(msg) %输出将导致XSS攻击。必须前端过滤index.jsp中用JSTLc:out value${user.username} /自动转义HTML特殊字符后端过滤MessageServlet中保存留言前用StringEscapeUtils.escapeHtml4(msg)Apache Commons Text数据库层SQL Server中对messages.content字段使用NVARCHAR而非VARCHAR支持Unicode安全存储。6.2.1StringEscapeUtils的零配置集成下载commons-text-1.10.0.jar放入WEB-INF/lib/代码中直接调用import org.apache.commons.text.StringEscapeUtils; // ... String safeContent StringEscapeUtils.escapeHtml4(userInput); // 即使输入scriptalert(1)/script输出为lt;scriptgt;alert(1)lt;/scriptgt;注意escapeHtml4()仅处理HTML实体对JavaScript上下文需用escapeEcmaScript()但JSP中% %输出到HTML文本节点故escapeHtml4()足够。6.3 CSRF防护为关键操作添加Token验证抽奖、修改密码等敏感操作需防CSRF。在draw.jsp中% // 生成唯一Token并存入Session String csrfToken UUID.randomUUID().toString(); session.setAttribute(csrf_token, csrfToken); % form actionDrawServlet methodpost input typehidden namecsrf_token value% csrfToken % button typesubmit开始抽奖/button /formDrawServlet中验证String tokenFromForm request.getParameter(csrf_token); String tokenFromSession (String) session.getAttribute(csrf_token); if (tokenFromForm null || !tokenFromForm.equals(tokenFromSession)) { request.setAttribute(error, 非法请求请刷新页面重试); request.getRequestDispatcher(draw.jsp).forward(request, response); return; } // 验证通过后立即清除Session中的Token一次性 session.removeAttribute(csrf_token);提示Token必须一次性使用且与Session绑定removeAttribute()防止重复提交这是CSRF防护的最小可行方案。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻