1. 项目概述
这个JSP+Servlet简易商城登录注册系统是一个典型的Java Web入门实战项目。作为电商系统的基础模块,登录注册功能承载着用户身份验证和权限控制的核心职责。我十年前刚入行时做的第一个项目就是类似的商城系统,当时踩了不少坑,现在回想起来依然记忆犹新。
使用JSP+Servlet技术栈搭建这类系统有几个明显优势:首先,这是Java EE最基础的技术组合,学习曲线平缓;其次,不需要引入复杂的框架,适合理解Web开发底层原理;最后,整个项目可以运行在轻量级的Tomcat服务器上,开发环境搭建非常简单。不过要注意的是,现代企业级项目通常会采用Spring等框架,这个简易版更适合教学和练手。
2. 技术选型与准备
2.1 开发环境配置
基础环境需要准备:
- JDK 1.8+(推荐OpenJDK 11)
- Apache Tomcat 9.x
- MySQL 5.7+或MariaDB
- Eclipse/IntelliJ IDEA开发工具
重要提示:Tomcat版本要与JDK版本匹配,JDK11建议使用Tomcat 9.x,避免兼容性问题。
数据库表设计是登录注册系统的核心,这里给出用户表的基础结构:
CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(100) NOT NULL, `email` varchar(100) NOT NULL, `phone` varchar(20) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `username_UNIQUE` (`username`), UNIQUE KEY `email_UNIQUE` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;2.2 项目目录结构
标准的Maven Web项目结构如下:
src/ ├── main/ │ ├── java/ # Java源代码 │ │ └── com/ │ │ └── example/ │ │ └── servlet/ # Servlet类 │ ├── resources/ # 配置文件 │ └── webapp/ # Web资源 │ ├── WEB-INF/ │ │ ├── lib/ # 依赖库 │ │ └── web.xml # 部署描述符 │ ├── css/ # 样式表 │ ├── js/ # JavaScript │ └── jsp/ # JSP页面 └── test/ # 测试代码3. 核心功能实现
3.1 用户注册模块
注册流程的关键点:
- 前端表单验证(JS)
- 服务端二次验证
- 密码加密存储
- 异常处理
注册Servlet示例代码:
@WebServlet("/register") public class RegisterServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); String email = request.getParameter("email"); // 参数校验 if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { request.setAttribute("error", "用户名和密码不能为空"); request.getRequestDispatcher("/register.jsp").forward(request, response); return; } // 密码加密 String encryptedPwd = DigestUtils.md5Hex(password + SALT); // 用户对象封装 User user = new User(); user.setUsername(username); user.setPassword(encryptedPwd); user.setEmail(email); // 数据库操作 try { UserService userService = new UserServiceImpl(); boolean result = userService.register(user); if(result) { response.sendRedirect("register_success.jsp"); } else { request.setAttribute("error", "注册失败,用户名可能已存在"); request.getRequestDispatcher("/register.jsp").forward(request, response); } } catch (SQLException e) { throw new ServletException("数据库操作异常", e); } } }3.2 用户登录模块
登录功能需要注意的安全要点:
- 防止SQL注入
- 密码比对方式
- Session管理
- 登录失败处理
登录验证的核心逻辑:
public boolean login(String username, String password) throws SQLException { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { conn = dataSource.getConnection(); String sql = "SELECT password FROM users WHERE username = ?"; stmt = conn.prepareStatement(sql); stmt.setString(1, username); rs = stmt.executeQuery(); if(rs.next()) { String dbPassword = rs.getString("password"); String inputPassword = DigestUtils.md5Hex(password + SALT); return dbPassword.equals(inputPassword); } return false; } finally { // 关闭资源 } }4. 前端页面实现
4.1 注册页面关键代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>用户注册</title> <style> .error { color: red; } </style> </head> <body> <h2>用户注册</h2> <form action="register" method="post" onsubmit="return validateForm()"> <div> <label>用户名:</label> <input type="text" name="username" id="username"> <span id="usernameError" class="error"></span> </div> <div> <label>密码:</label> <input type="password" name="password" id="password"> <span id="passwordError" class="error"></span> </div> <div> <label>邮箱:</label> <input type="email" name="email" id="email"> </div> <button type="submit">注册</button> </form> <script> function validateForm() { let valid = true; const username = document.getElementById('username').value; const password = document.getElementById('password').value; if(username.length < 4) { document.getElementById('usernameError').innerText = '用户名至少4个字符'; valid = false; } else { document.getElementById('usernameError').innerText = ''; } if(password.length < 6) { document.getElementById('passwordError').innerText = '密码至少6个字符'; valid = false; } else { document.getElementById('passwordError').innerText = ''; } return valid; } </script> </body> </html>5. 系统优化与安全
5.1 密码安全增强
- 使用更安全的加密算法:
public static String encrypt(String password) { int iterations = 10000; char[] chars = password.toCharArray(); byte[] salt = getSalt(); PBEKeySpec spec = new PBEKeySpec(chars, salt, iterations, 64 * 8); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] hash = skf.generateSecret(spec).getEncoded(); return iterations + ":" + toHex(salt) + ":" + toHex(hash); }5.2 防止重复提交
在注册页面添加Token机制:
<% String token = UUID.randomUUID().toString(); session.setAttribute("TOKEN_IN_SESSION", token); %> <input type="hidden" name="token" value="<%=token%>">然后在Servlet中验证:
String clientToken = request.getParameter("token"); String serverToken = (String) request.getSession().getAttribute("TOKEN_IN_SESSION"); if(serverToken == null || !serverToken.equals(clientToken)) { // 重复提交处理 return; } request.getSession().removeAttribute("TOKEN_IN_SESSION");6. 常见问题排查
6.1 JSP文件未生成问题
如果发现Tomcat的work目录没有生成JSP对应的Java文件,可以检查:
- 确认Tomcat配置文件中
<Host>标签的autoDeploy和reloadable属性为true - 检查JSP文件是否有语法错误
- 查看Tomcat日志中的编译错误
- 确认JSP文件放在了正确的位置(webapp目录下)
6.2 数据库连接池配置
推荐使用Tomcat JDBC连接池,在context.xml中添加:
<Resource name="jdbc/ShopDB" auth="Container" type="javax.sql.DataSource" maxTotal="100" maxIdle="30" maxWaitMillis="10000" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/shop_db?useSSL=false&characterEncoding=utf8"/>然后在web.xml中引用:
<resource-ref> <description>DB Connection</description> <res-ref-name>jdbc/ShopDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>7. 项目部署与测试
7.1 Tomcat部署步骤
- 将项目打包成WAR文件:
mvn clean package将生成的WAR文件复制到Tomcat的webapps目录
启动Tomcat:
./catalina.sh run- 访问应用:
http://localhost:8080/your-project-name/7.2 基础功能测试用例
| 测试场景 | 测试步骤 | 预期结果 |
|---|---|---|
| 正常注册 | 输入合规的用户名、密码和邮箱 | 注册成功,跳转到成功页面 |
| 用户名重复 | 输入已存在的用户名 | 提示"用户名已存在" |
| 密码太短 | 输入5位密码 | 提示"密码至少6位" |
| 正常登录 | 使用正确凭据登录 | 登录成功,建立会话 |
| 错误密码 | 使用错误密码登录 | 提示"用户名或密码错误" |
8. 扩展与进阶
8.1 添加验证码功能
使用Kaptcha库实现验证码:
- 添加依赖:
<dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency>- 在web.xml中配置:
<servlet> <servlet-name>Kaptcha</servlet-name> <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Kaptcha</servlet-name> <url-pattern>/kaptcha.jpg</url-pattern> </servlet-mapping>- JSP页面中使用:
<img src="kaptcha.jpg" onclick="this.src='kaptcha.jpg?'+Math.random()"> <input type="text" name="kaptcha">8.2 记住我功能实现
使用Cookie实现记住登录状态:
// 登录成功后 if("on".equals(request.getParameter("remember"))) { Cookie usernameCookie = new Cookie("username", username); usernameCookie.setMaxAge(7 * 24 * 60 * 60); // 7天 response.addCookie(usernameCookie); }在登录页面自动填充:
<% Cookie[] cookies = request.getCookies(); String savedUsername = ""; if(cookies != null) { for(Cookie cookie : cookies) { if("username".equals(cookie.getName())) { savedUsername = cookie.getValue(); break; } } } %> <input type="text" name="username" value="<%=savedUsername%>">9. 性能优化建议
- 使用Filter实现Gzip压缩:
@WebFilter("/*") public class CompressionFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String encodings = request.getHeader("Accept-Encoding"); if(encodings != null && encodings.contains("gzip")) { CompressionResponseWrapper wrappedResp = new CompressionResponseWrapper(response); wrappedResp.setHeader("Content-Encoding", "gzip"); chain.doFilter(request, wrappedResp); wrappedResp.finish(); } else { chain.doFilter(request, response); } } }- 启用浏览器缓存静态资源:
<filter> <filter-name>ExpiresFilter</filter-name> <filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class> <init-param> <param-name>ExpiresByType text/css</param-name> <param-value>access plus 1 week</param-value> </init-param> <init-param> <param-name>ExpiresByType application/javascript</param-name> <param-value>access plus 1 month</param-value> </init-param> </filter>10. 项目总结与反思
这个简易商城登录注册系统虽然功能基础,但涵盖了Java Web开发的多个核心知识点。在实际开发过程中,我发现有几个关键点特别值得注意:
密码安全绝对不能马虎,简单的MD5加密已经不够安全,应该使用加盐的PBKDF2或者bcrypt算法。
输入验证要前后端同时进行,前端验证提升用户体验,后端验证保证数据安全。
错误处理要友好,不能直接把异常信息暴露给用户,但开发时应该记录详细日志。
数据库连接等资源一定要正确关闭,否则会导致内存泄漏。
这个项目还可以进一步扩展,比如添加手机短信验证、第三方登录(微信/QQ)、权限管理等功能。对于想学习Java Web开发的新手来说,从这样一个具体的小项目入手,逐步扩展功能,是很好的学习路径。