ARTICLE DETAIL

资讯详情

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

JSP+JavaBean+Servlet实战:从零搭建登录注册与留言板系统

JSP+JavaBean+Servlet实战:从零搭建登录注册与留言板系统 简介一套基于JSPJavaBeanServlet的JavaWeb经典入门项目采用简洁的MVC分层设计面向初学JavaWeb的开发者覆盖登录注册、留言板、分页三大功能模块。项目中model层由JavaBean封装数据库连接与增删改查方法连接的webapp数据库未设置密码view层使用JSP生成动态页面controller层通过Servlet拦截请求、调用业务方法并完成页面转发三层职责清晰便于分段阅读与调试。压缩包共78个文件包含14个Java源文件、14个JSP页面、14个class编译文件以及CSS样式、XML与properties配置、SQL建库脚本、JAR依赖库等足以支撑项目在IDE中直接导入运行并借助SQL脚本快速重建数据库压缩包整体仅1.86MB。目前已有1611人浏览学习。借助它可快速理解MVC各层如何协作、JDBC如何读写数据库、分页查询如何实现以及留言板功能的设计思路很适合作为JavaWeb课程设计或入门练习的系统性参考。1. 为什么毕业设计和课程设计仍然在用 JSPJavaBeanServlet 写登录注册和留言板“用 jsp javabean servlet 实现登录注册以及留言板”这行字在题目里出现不是巧合它是 Web 开发里最典型的“能用且够用”结构。不靠任何框架Servlet 处理请求、JavaBean 封装和携带数据、JSP 负责把页面输出给浏览器一次完整的请求-响应周期全程不超过五个类。它解决的是一个没有接触过企业级框架的学生如何在两周内跑通一套带数据存储的真实系统。同时它也能回答一个本质问题——MVC 到底在代码里怎么落地。网上能搜到的 servlet demo 大多停留在打印一句 Hello World和真实项目之间隔着一条数据库连接、一套页面渲染和一堆异常处理。这篇文章适合第一次做完整 Web 系统的初学者也适合需要快速交付演示程序、但又不想引入 Spring 全家桶的从业者。2. 先定架构再建表这套组合里的数据到底怎么流把 jsp、javabean、servlet 三个角色摆正后面写代码才不会变成一坨 Servlet 里拼 HTML 的老式写法。我的建议是记住一句口诀“浏览器只认识 JSPServlet 只做通信兵JavaBean 只背数据包。”2.1 MVC 分工与实际包结构严格来说这是标准的 MVC 变体JSP 是 ViewServlet 是 ControllerJavaBean 是 Model。一个请求进来后数据流的顺序是固定的浏览器把表单参数提交给 Servlet 的 doPostServlet 用 request.getParameter 取出参数参数封装进 JavaBean 对象DAO 层用 JDBC 把 JavaBean 的数据写进 MySQL 或读出来Servlet 把结果放进 request 或 session最后 forward 或 redirect 到某个 JSP由 JSP 负责渲染。很多初学同学喜欢把 SQL 直接写在 Servlet 里把 for 循环写在 JSP 的% %里这种写法不是不能跑而是后面改需求时每改一次都像拆一次炸弹。我一般会按下面这个包结构组织工程这也是课程设计里最容易被答辩老师接受的布局com.example.entity User.java / Message.java com.example.dao UserDao.java / MessageDao.java com.example.servlet LoginServlet / RegisterServlet / MessageServlet com.example.filter LoginFilter.java com.example.util DBUtil.java / MD5Util.javaentity 里就是纯 JavaBean属性对应数据库字段dao 里只放 JDBC 操作servlet 里做参数接收、校验和页面跳转util 放数据库连接和加密工具。如果你连 web.xml 都不熟那就先别急着给 Servlet 配映射直接用WebServlet(/login)注解省掉一批配置。前提是 Tomcat 7 以上现在实验室里基本都满足。2.2 数据库表设计用户表和留言表的字段与约束留言板再简单也必须有用户和留言两张表这个设计是整篇文章的地基。直接执行下面这段 SQLCREATE DATABASE IF NOT EXISTS login_demo DEFAULT CHARSET utf8mb4; USE login_demo; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, salt CHAR(8) NOT NULL, email VARCHAR(120), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_message ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, content VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user_id (user_id), CONSTRAINT fk_msg_user FOREIGN KEY (user_id) REFERENCES t_user (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明t_user 的 password 长度设为 64 是为了容纳 MD5 摘要后的 32 位十六进制再加盐如果只设 20 位后面做加密时数据会写不进去这是很多人忽略的字段约束问题。t_message 用外键关联 t_userON DELETE CASCADE表示删除用户时其留言一并删除保证数据不会变成孤儿记录。字符集统一使用 utf8mb4 而不是 utf8原因很简单utf8 在 MySQL 里存不下 emoji 表情留言内容里一旦出现表情符号整条 insert 直接报错这种报错还会把事务回滚查半天才能发现是字符集问题。参数说明外键这行的CONSTRAINT fk_msg_user是约束名可自定义KEY idx_user_id是普通索引目的是加快按用户查留言的速度。数据量没过万时索引体感不明显但这是答辩时一个容易拿到分的点写上不亏。2.3 两个核心 JavaBeanUser 和 Message 怎么写才不越界JavaBean 在这一套组合里只做一件事定义属性和对应的 getter/setter。不要在里面写数据库连接也不要写业务判断保持纯粹后面 Servlet 里才好复用。package com.example.entity; public class User { private int id; private String username; private String password; private String salt; private String email; private String createdAt; public User() {} public int getId() { return id; } public void setId(int id) { this.id id; } public String getUsername() { return username; } public void setUsername(String username) { this.username username; } public String getPassword() { return password; } public void setPassword(String password) { this.password password; } public String getSalt() { return salt; } public void setSalt(String salt) { this.salt salt; } public String getEmail() { return email; } public void setEmail(String email) { this.email email; } public String getCreatedAt() { return createdAt; } public void setCreatedAt(String createdAt) { this.createdAt createdAt; } }Message 类的字段则是 id、userId、content、createTime其中 createTime 因为是数据库自动生成JavaBean 里对应的是 String 类型从 ResultSet 取出来的是 java.sql.Timestamp直接 toString 会得到一串带毫秒的格式展示时做一次截断即可。为什么不建议在 JavaBean 里加一个showTime()方法因为那是展示逻辑属于 JSP 的职责范围。纯数据模型的好处是以后换用 MyBatis 或者 JSON 序列化时这个类一行都不用改。3. 登录和注册表单提交、MD5、Session 和 Filter 一次讲透登录注册是这套系统的门面也是安全审查的重点。很多老代码直接用明文存密码答辩时老师一打开数据库就能看到“123456”一排排放着这个分基本别想拿。这里采用 MD5 加盐的方式不加第三方依赖纯 JDK 自带 MessageDigest 就能实现。3.1 注册链路从表单到数据库的六个步骤注册页面的表单长什么样不重要重要的是提交到哪个路径、Servlet 里做了什么。一个标准的注册流程包含六个步骤接收参数、校验空值、检查用户名是否存在、密码加密、写入数据库、跳转登录页。package com.example.servlet; import com.example.dao.UserDao; import com.example.entity.User; import com.example.util.MD5Util; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; WebServlet(/register) public class RegisterServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); String email req.getParameter(email); if (username null || username.isEmpty() || password null || password.isEmpty()) { req.setAttribute(error, 用户名和密码不能为空); req.getRequestDispatcher(/register.jsp).forward(req, resp); return; } UserDao dao new UserDao(); if (dao.findByUsername(username) ! null) { req.setAttribute(error, 用户名已存在); req.getRequestDispatcher(/register.jsp).forward(req, resp); return; } String salt MD5Util.randomSalt(); User user new User(); user.setUsername(username); user.setPassword(MD5Util.md5(password salt)); user.setSalt(salt); user.setEmail(email); dao.insert(user); resp.sendRedirect(req.getContextPath() /login.jsp?registered1); } }逻辑说明所有参数先过一遍空值校验任何一个为空就直接 forward 回注册页同时把错误信息放进 request。检查用户名重复是在数据库层面用SELECT id FROM t_user WHERE username?来做的用 LIMIT 1 即可这个查询是登录功能里最高频的操作索引 uk_username 在这里生效。密码加密采用“原文加盐再做 MD5”的方式盐是每次注册时随机生成的 8 位字符串和 MD5 值一起入库。为什么要加盐而不是直接 md5(password)因为相同的密码在不同用户身上会得到不同的摘要即使数据库泄露也无法通过彩虹表反查明文。参数说明WebServlet(/register)里的斜杠是必须的路径从应用根目录算起这是 Servlet 3.0 注解写法省掉了 web.xml 的一堆servlet-mapping配置。randomSalt 的实现可以用UUID.randomUUID().toString().substring(0, 8)这个方法在 util 包里维护工具类放在 util 包而不是 servlet 包是所有类的通用依赖。3.2 登录校验与 Session 状态保持登录成功后的核心是 Session。Session 里存什么、存多久、什么时候失效直接决定整个系统的安全边界。登录成功后我只把 User 对象的 id 和 username 放进 Session密码和盐一概不进这是为了避免 JSP 页面里不小心把用户密码打印出来。WebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); UserDao dao new UserDao(); User user dao.findByUsername(username); if (user null || !user.getPassword().equals(MD5Util.md5(password user.getSalt()))) { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /message/list); } }逻辑说明查询用户时把完整的 User 对象取出来拿到盐后再对用户输入的密码做同样的 MD5 运算和库里的值比对。这里有个细节user.getPassword().equals(...)是拿数据库摘要和实时计算摘要比而不是拿明文比后者在日志里会暴露密码。登录成功后执行req.getSession()如果当前请求没有关联的会话容器会自动创建一个有就用旧的保证同一个浏览器多次请求拿到同一个 Session。Session 默认有效期在 Tomcat 里是 30 分钟超过 30 分钟无操作会自动失效这个时间可以在 web.xml 里通过session-config覆盖。跳转用重定向而不是 forward 的原因后面留言板章节会详细讲核心就是防表单重复提交。3.3 用 Filter 拦截未登录请求避免整个站点裸奔登录接口写完后还要回答一个问题用户不登录能不能直接访问留言列表页面答案是不能这需要 Filter 来完成。Filter 是 Java Web 里最原生的拦截器不用引入 Spring Security 也能实现基本的页面保护。package com.example.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) servletRequest; HttpServletResponse resp (HttpServletResponse) servletResponse; String path req.getRequestURI().substring(req.getContextPath().length()); String contextPath req.getContextPath(); if (path.startsWith(/login) || path.startsWith(/register) || path.equals(/index.jsp) || path.startsWith(/static/)) { chain.doFilter(servletRequest, servletResponse); return; } HttpSession session req.getSession(false); if (session null || session.getAttribute(loginUser) null) { resp.sendRedirect(contextPath /login.jsp); return; } chain.doFilter(servletRequest, servletResponse); } }逻辑说明Filter 的拦截范围是/*即所有请求先进过滤器再进 Servlet。白名单机制处理登录、注册、首页、静态资源这四类路径其余所有路径都必须先验证 Session。关键点在req.getSession(false)——传 false 表示如果当前请求没有关联 Session 就返回 null而不是自动创建一个新的这样可以避免未登录用户每次访问都被迫生成一个无用的会话。未登录时重定向到登录页重定向必须带 contextPath否则在部署路径不是根目录的应用上会直接 404。静态资源放行的路径要与你的实际目录对应如果 CSS 放在 webapp/css 下就改成/css/不要写错。4. 留言板怎么写得清爽列表循环、发留言、删除权限与分页留言板是这个项目里功能最完整的部分也是最能体现工程能力的一块。这里分享一个可复用的干净写法JSP 只做展示和表单Servlet 只做参数接收和跳转DAO 只做增删查。4.1 留言列表页的 JSP 渲染与个人信息展示留言列表页其实就是一个最简单的 jsp 个人信息展示页面——每条留言展示用户名、发布时间、内容附带一个删除入口。用一个 JSTL c:forEach 循环配合 EL 表达式取值这是老项目里最不容易出错的写法。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % page contentTypetext/html;charsetUTF-8 languagejava % div classmessage-list c:forEach items${messageList} varmsg div classmessage-item p classmessage-content${msg.content}/p p classmessage-meta 发布于 ${msg.createTime} c:if test${sessionScope.loginUser.id msg.userId} a href${pageContext.request.contextPath}/message/delete?id${msg.id}删除/a /c:if /p /div /c:forEach /div逻辑说明${messageList}是 Servlet 放进 request 里的集合${msg.content}会自动调用 Message 类的 getContent 方法这一整套 EL 表达式能正常工作需要 JSP 页面顶部引入 JSTL 标签库并且在 pom.xml 或 WEB-INF/lib 里放 jstl 和 standard 两个 jar 包。删除入口用条件判断包住只有当前登录用户是留言作者时才显示删除按钮这个条件在页面上用 EL 从 sessionScope 里取 loginUser 的 id和留言的作者 id 做比较不需要写% %脚本片段。这里有个容易忽略的点JSP 页面里的msg.createTime如果是从数据库取出的 TimestampEL 表达式打印出来会是带毫秒的长字符串看起来很不专业。处理办法是在 Message 类里加一个 getShowTime 方法内部做日期格式化在 EL 表达式里继续用msg.showTime取值JavaBean 允许这种简化的只读属性存在但不建议让 JSP 直接 new SimpleDateFormat 去格式化。4.2 发留言为什么必须用重定向回列表页发留言的表单提交到 MessageServlet核心动作是 insert 成功后重定向列表页。这里有一个常见的翻车场景用 forward 跳转列表页用户按一下 F5浏览器把刚才的表单请求原样重发一次数据库里就多了一条一模一样的留言。解决方法是 Post/Redirect/Get 模式简称 PRG。WebServlet(/message/add) public class MessageAddServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); User loginUser (User) req.getSession().getAttribute(loginUser); if (loginUser null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } String content req.getParameter(content); if (content null || content.trim().isEmpty()) { req.setAttribute(error, 留言内容不能为空); req.getRequestDispatcher(/message/list).forward(req, resp); return; } Message message new Message(); message.setUserId(loginUser.getId()); message.setContent(content.trim()); MessageDao dao new MessageDao(); dao.insert(message); resp.sendRedirect(req.getContextPath() /message/list); } }逻辑说明PRG 的核心是让浏览器在提交完成之后收到一个 302 响应浏览器随后自动发起一次 GET 请求去访问列表页。此时地址栏里已经变成/message/listF5 刷新触发的是 GET 请求不会再带上 POST 表单数据重复提交问题从源头消失。留一句重要体验留言成功后给用户一个视觉反馈最简单的是在列表页顶部放一个一行提示“留言成功”这个提示可以用一次性的 sessionAttribute 实现读一次就清掉否则刷新后提示还在。参数说明content.trim()是必要的用户在文本框里打了空格也算空内容这种边界条件在答辩演示时很容易被打出来。这里的空值校验和注册 Servlet 里的一致保持全项目的校验风格统一比每处写得五花八门更重要。4.3 删除权限校验和分页参数两个必调参数删除留言的入口是上一小节那句删除链接但前端藏住按钮不代表接口安全Servlet 里必须再次校验登录状态和作者身份。因为任何人都可以手动在地址栏输入/message/delete?id18发起删除请求完全没有页面上的条件限制。一个安全的删除逻辑实现如下WebServlet(/message/delete) public class MessageDeleteServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { User loginUser (User) req.getSession().getAttribute(loginUser); if (loginUser null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } int id Integer.parseInt(req.getParameter(id)); MessageDao dao new MessageDao(); Message message dao.findById(id); if (message null) { resp.sendRedirect(req.getContextPath() /message/list); return; } if (message.getUserId() ! loginUser.getId()) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } dao.deleteById(id); resp.sendRedirect(req.getContextPath() /message/list); } }这样处理后即使有人绕过页面直接调用接口没有登录或不是留言作者都会被拦下。这里的用户 id 比较必须用 int 的基本类型判断因为!在自动拆箱时如果有一侧是 null 会抛空指针取 session 里的 user 时先判空正是为了挡住这个异常。数据量小的时候不需要分页但留言板往往会越积越多。给列表页加一个最朴素的分页pageNo 表示当前页pageSize 固定为 5用 SQL 的 LIMIT 和 count 查询配合。代码层面就是三句话int pageNo 1; try { pageNo Integer.parseInt(req.getParameter(page)); } catch (NumberFormatException ignore) {} int pageSize 5; int total messageDao.count(); int totalPages (int) Math.ceil((double) total / pageSize); ListMessage list messageDao.findPage(pageNo, pageSize); req.setAttribute(messageList, list); req.setAttribute(totalPages, totalPages); req.setAttribute(currentPage, pageNo);对应的 DAO 里findPage执行 SQL 是SELECT * FROM t_message ORDER BY id DESC LIMIT ?, ?第一个参数传(pageNo - 1) * pageSize第二个传 pageSize。页码超出范围时比如删掉最后一页最后一条数据后再点上一页查出来是空列表此时要在 Servlet 里把 pageNo 钳制到 1 和 totalPages 之间不然空页面看起来像一个 bug。5. 从乱码到404这套代码最常见的五个坑这一章是这套系统交付前必须过一遍的排错清单每一条都是我实际踩过的按“现象 → 原因 → 解决”来写你会发现大部分诡异问题背后都是配置遗漏而不是代码逻辑错。5.1 POST 中文全部变成问号现象注册用户名“张三”提交到数据库后变成“”或者 MySQL 客户端查出来是完整的但 JSP 页面显示乱码。原因请求参数编码、页面编码、数据库连接编码三处没有对齐。解决在 LoginFilter 或每一个 Servlet 的 doPost 第一行调用req.setCharacterEncoding(UTF-8)在 JDBC 连接 URL 里加characterEncodingutf8JSP 页面顶部声明pageEncodingUTF-8。三者齐了才能保证中文从浏览器到 MySQL 走一圈不变形。实际项目中最省事的做法是在 Filter 里对所有请求做 encode而不在每个 Servlet 里重复写。5.2 MySQL 8 下加载驱动直接 ClassNotFound现象程序一启动连数据库就抛ClassNotFoundException但连接字符串和用户名密码全都检查过没错。原因MySQL 驱动版本和写的老驱动类名不匹配。解决MySQL 8 及以上版本的 JDBC 驱动类名是com.mysql.cj.jdbc.Driver旧版是com.mysql.jdbc.Driver同时连接 URL 里要追加serverTimezoneAsia/Shanghai或者serverTimezoneUTC否则驱动会由于时区不明而拒绝连接。如果你的 pom.xml 引的是 mysql-connector-java 8.x请直接使用新类名旧类名在这代驱动里已经被移除不存在兼容一说。5.3 注册成功后 CSS 全部失效页面裸奔现象登录页能正常访问时样式是好的注册成功跳转后页面只有文字没有样式按 F12 全是 404。原因页面里用了相对路径引用 CSS比如hrefcss/style.css当 URL 从/login.jsp变成/message/list后浏览器解析相对路径的基准目录变了。解决在所有 JSP 头部写base href%request.getContextPath()%/或在引用的 href 里完整地写${pageContext.request.contextPath}/css/style.css。这是老工程的老问题养成全站写绝对路径的习惯可以避免这类问题。5.4 F5 刷新一次存一条重复留言现象发布一条留言后按 F5列表里出现两条同样的内容如果是删除操作刷新后还能看到“已删除”的记录。原因表单 POST 提交后如果使用 forward 跳转结果页地址栏仍是原来的 POST 地址刷新即重放。解决照第 4 章的做法提交完成后调用resp.sendRedirect让浏览器跳转到 GET 请求的列表页。还有一个容易漏的细节留言内容的 JS 校验挡住空提交没问题但挡不住双击提交按钮——两个几乎同时到达的 POST 请求会各自通过检查各插入一条。稳妥的做法是在前端加一次性禁用按钮标记这个比后端去重简单得多。5.5 登录成功后跳回列表页还是未登录现象重定向到/message/list时又被 Filter 踢回登录页似乎 Session 没有生效。原因Servlet 里是用request.getSession()存了用户但 Filter 里用req.getSession(false)取不到对象——往往是因为登录成功后访问的不是同一个应用根或者跳转到的地址跨了 contextPath。解决跳转时统一使用req.getContextPath()拼路径不要写死/login.jsp否则部署在带应用名的路径下就会出这种问题。调试方法很简单在 Filter 里临时打印req.getRequestURI()和req.getSession(false)一眼就能看出是跳转路径少了前缀还是 Filter 拦截范围判断失准。5.6 留言里带 emoji 表情整个 insert 报错现象用户在留言框里发了一个笑脸表情提交后页面报 SQL 错误而且提示信息里没有明显的字段名。原因数据库表和连接 URL 的字符集是 utf8而 utf8 在 MySQL 中只支持三字节的编码emoji 是四字节。解决建表时用DEFAULT CHARSETutf8mb4同时把连接 URL 中的characterEncodingutf8改为characterEncodingUTF-8注意这里字节意义上的 utf8mb4 不是写到 URL 里的值数据库连接层面统一成 UTF-8 即可。如果项目已经建完表单独对表和字段执行ALTER TABLE t_message CONVERT TO CHARACTER SET utf8mb4;就能修好不必删库重来。6. 给登录补一个验证码不引入框架的纯 Servlet 做法登录接口如果没有任何验证码会被脚本用撞库的方式刷用户名和密码这个风险在你的应用部署到公网服务器时尤其明显。课程设计只要做到“有用即可”的验证码又不引第三方框架最省事的方案是用 JDK 自带的 Graphics2D 绘制图片。核心逻辑只有三步生成随机字符放进 Session用图画出来输出成图片登录时比对用户输入和 Session 里的值。关键点在于验证码要一次有效校验成功后立即把 Session 里的验证码删掉否则同一个验证码能被反复使用。一个能用的简化实现是把固定长度字符画进 BufferedImage再通过 ServletResponse 的输出流写回浏览器。要留意的是图片响应必须设置setContentType(image/jpeg)和禁止缓存的头否则浏览器可能会把验证码图片缓存住导致刷新也不换内容。图片坐标里的纵向偏移和字符间距都可以在生成图片的循环参数里调整这也是搜索词里“jsp 图片如何对坐标定位”最常见的应用场景——验证码字符不是画在固定像素点而是按字符宽度递增定位如果你以后做头像裁剪起始 x、y 和宽高四个值一存后面 Java2D 裁剪的套路同源。给登录页加上验证码后LoginServlet 里要比对表单提交的code参数和 Session 里的验证码字符串忽略大小写比对后立刻session.removeAttribute(code)。不要小看这十来行代码它在答辩时是一个拿得出手的加分段。我自己做这类老项目形成的习惯是登录注册这条链路永远先从数据库表和 Filter 画起再写 Servlet最后补页面这个顺序能避免一半的改稿返工留言板哪怕数据量很小分页和 PRG 也会先写上因为演示现场永远是最容易按 F5 的场合。希望帮到你。本文还有配套的精品资源点击获取
返回列表