小项目实战:登录注册 + 留言板
把这一页的零件全拼起来:用户能注册、能登录、登录后能留言、能看别人留的言。数据库是 MySQL,表结构先建好,后面的代码围绕它转。这个项目跑通,你就理解了一个传统 Web 系统的骨架。
第一步:建库建表(MySQL 里执行)
CREATE DATABASE board_db CHARACTER SET utf8mb4;
USE board_db;
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL -- 生产环境存 BCrypt 哈希,别存明文!
);
CREATE TABLE messages (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
content VARCHAR(500),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
第二步:注册 Servlet(RegisterServlet)
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
String username = req.getParameter("username");
String password = req.getParameter("password");
String sql = "INSERT INTO users(username, password) VALUES(?, ?)";
// 省略从 HikariCP 拿连接、PreparedStatement 绑定参数的样板——参考第 4、5 章
// 成功后重定向到登录页;用户名重复(唯一索引冲突)就回注册页提示
resp.sendRedirect("/login.jsp");
}
}
第三步:登录 Servlet + 留言列表 Servlet
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
String username = req.getParameter("username");
String password = req.getParameter("password");
// SELECT * FROM users WHERE username=? AND password=? —— PreparedStatement
boolean ok = /* 查库看有没有这一行 */;
if (ok) {
req.getSession().setAttribute("loginUser", username); // 写 Session
resp.sendRedirect("/board"); // 跳留言板
} else {
req.setAttribute("error", "用户名或密码错");
req.getRequestDispatcher("/login.jsp").forward(req, resp);
}
}
}
@WebServlet("/board")
public class BoardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
// SELECT m.content, u.username, m.created_at
// FROM messages m JOIN users u ON m.user_id = u.id ORDER BY m.created_at DESC
List<String[]> rows = /* 查出来封装成 List */;
req.setAttribute("rows", rows);
req.getRequestDispatcher("/board.jsp").forward(req, resp); // 交给 JSP 展示
}
}
第四步:board.jsp 用 JSTL 把留言画出来
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://xmlns.jcp.org/jsp/jstl/core" %>
<h2>留言板(欢迎你,${loginUser})</h2>
<ul>
<c:forEach items="${rows}" var="r">
<li><b>${r[1]}</b> 说:${r[0]} <small>${r[2]}</small></li>
</c:forEach>
</ul>
<form action="/post" method="post">
<textarea name="content"></textarea>
<button>发表</button>
</form>
第五步:发表留言 PostServlet(登录后才能用)
@WebServlet("/post")
public class PostServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
// 注意:LoginFilter 已经在 /admin/* 拦了未登录,
// 这里从 Session 拿当前用户,别信表单里传的 user_id!
String username = (String) req.getSession().getAttribute("loginUser");
String content = req.getParameter("content");
// INSERT INTO messages(user_id, content) VALUES(?, ?)
// 用 PreparedStatement,content 里带什么引号都安全
resp.sendRedirect("/board"); // 重定向回列表,避免刷新重复发表
}
}
这个小项目的目录长这样(Maven 标准结构)
board/
├── pom.xml # 依赖:servlet-api、mysql 驱动、JSTL、HikariCP
└── src/main/
├── java/com/example/board/
│ ├── RegisterServlet.java
│ ├── LoginServlet.java
│ ├── BoardServlet.java
│ ├── PostServlet.java
│ ├── EncodingFilter.java
│ └── LoginFilter.java
└── webapp/
├── login.jsp
├── register.jsp
└── board.jsp
这个项目最常见的三个报错
| 报错 | 八成是什么原因 |
|---|---|
| ClassNotFoundException: Driver | MySQL JDBC 驱动包没加进项目(Maven 依赖没导,或 lib 里没 jar)。 |
| Access denied for user 'root' | JDBC URL 里用户名密码写错,或 MySQL 没给这个账号权限。 |
| 登录了却跳回登录页 | Session 过期、换浏览器测了,或 LoginFilter 里取 Session 属性名和登录时塞的不一致。 |
论这个项目练到了哪些点
一张请求链路图:浏览器 → EncodingFilter(统一编码)→ LoginFilter(没登录拦回 login.jsp)→ BoardServlet(JDBC 查 messages 表)→ request 塞数据 → forward 给 board.jsp(JSTL 遍历)→ 浏览器看到留言列表。注册、登录用 Session 维持状态,PostServlet 把留言 INSERT 回数据库。
扩展作业:① 加 BCrypt 密码哈希(别存明文);② 留言内容用 c:out 输出防 XSS;③ 登出按钮 session.invalidate();④ 分页。做完这四样,这个小项目就能写进简历"项目经历"了。
1.(设计题)PostServlet 里为什么从 Session 拿 user_id,而不是从表单读?
查看答案
答案:表单里的 user_id 用户能随便改,从 Session 拿才是真实登录用户。信 Session 不信表单。
2.(排错题)登录成功后又跳回登录页?
查看答案
答案:① LoginFilter 里取的 Session 属性名和登录时塞的不一致;② Session 过期;③ 换浏览器测了(Cookie 没带)。
3.(思考题)密码为什么不能明文存数据库?怎么存?
查看答案
答案:数据库一脱库用户全线裸奔。用 BCrypt/Argon2 哈希加盐,不可逆。验证时把输入哈希后和库里存的比。