☰
JavaWeb分层架构教学项目实战:Servlet+JSP+JDBC商城详解
2026/9/29 16:17:37 网站建设 项目流程

简介:本资源是一个基于JavaWeb技术栈开发的华为商城实战项目,面向Java初学者与Web开发入门者,帮助系统掌握Servlet、JSP、MySQL协同构建电商系统的核心流程。压缩包共2个文件(1个RAR源码包+1个SQL数据库脚本),总大小58.24MB;其中RAR包含完整Web应用结构(WEB-INF配置、Java类、JSP页面及依赖库),SQL文件提供建表语句与初始数据,开箱即用搭建本地运行环境。已有961人学习下载,项目覆盖用户注册登录、商品浏览、购物车管理、订单处理等典型业务模块,代码结构清晰、注释规范,特别适合通过可运行实例理解MVC分层思想、JDBC数据库交互及JSP动态页面渲染机制,是JavaWeb工程化实践的优质参考范例。

1. 这不是华为官方商城,而是一套被反复用于JavaWeb教学与面试复现的典型分层架构实战项目

“javaweb华为商城.zip”——你在牛客网面经帖、CSDN下载页、GitHub旧仓库、甚至某宝Java课程配套资料里见过这个名字,大概率没点开过。它既不是华为内部系统,也不对接真实支付链路,但却是国内高校《Java Web开发》课程结课设计最常指定的选题模板,也是Java初级岗面试官手里那道“请现场画出你做过的Web项目分层结构”的具象锚点。这个压缩包解压后通常包含:src/下标准Servlet+JSP+JDBC三层结构、WebContent/里带简单CSS和jQuery的静态页面、一个db/目录塞着MySQL建表SQL和初始数据(用户、商品、订单三张表)、外加一份写着“管理员账号admin/123456”的txt说明。它不跑Spring Boot,不接Redis,不走RESTful,但它把HTTP请求怎么进Filter、怎么到Servlet、怎么查数据库、怎么跳JSP、怎么用EL表达式渲染列表——这些在现代框架里被自动封装的“黑匣子”,一层层掰开给你看。适合刚学完Servlet生命周期、正卡在“为什么我写的登录校验总跳不过去”阶段的开发者;也适合需要快速搭一个可演示、可调试、无外部依赖的JavaWeb最小闭环来验证某个中间件集成逻辑的工程师。别指望它能扛住并发,但它的每一行代码,都经得起你打断点、单步进、翻源码。


2. 从解压到启动:本地Tomcat部署的最小可行路径(含JDK8/Tomcat9兼容性实测)

这个zip包本质是一个传统Java Web Application Archive(WAR)结构的源码工程,不是Maven项目,没有pom.xml。这意味着你不能直接用IDEA点Run,必须走经典Servlet容器部署流。我用的是JDK 8u291 + Tomcat 9.0.83(这是目前与该类老项目兼容性最好的组合,JDK11+会因javax.servlet.*包路径变更直接报错,Tomcat10+则因jakarta.servlet.*命名空间迁移彻底无法识别web.xml)。下面步骤已在Windows 10、Ubuntu 22.04、macOS Sonoma三平台验证。

2.1 解压与目录结构确认:认准WEB-INF/web.xml是灵魂

解压后,你会看到类似这样的根目录:

javaweb_huawei_mall/ ├── src/ # Java源码,含com.huawei.mall包 ├── WebContent/ # Web资源根目录(等价于webapp) │ ├── WEB-INF/ │ │ ├── web.xml # 关键!Servlet注册、Filter配置、欢迎页都在这 │ │ └── lib/ # jar包全在这里,没maven,全靠手动拷 │ ├── index.jsp │ ├── login.jsp │ └── ... ├── db/ │ └── huawei_mall.sql # MySQL建库建表脚本 └── README.txt

提示:WebContent是Eclipse时代的老约定,对应IntelliJ里的webapp目录。若你用IDEA打开,需右键项目 →Add Framework Support→ 勾选Web Application,并手动指定WebContent为Web资源根目录,WebContent/WEB-INF/web.xml为部署描述符。

2.2 数据库初始化:MySQL 5.7兼容性处理(utf8mb4与ONLY_FULL_GROUP_BY)

db/huawei_mall.sql通常包含建库、建表、插入测试数据三段。执行前务必确认你的MySQL版本与SQL语法匹配:

-- 典型建表语句(注意ENGINE=InnoDB和CHARSET=utf8mb4) CREATE DATABASE IF NOT EXISTS huawei_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE huawei_mall; CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, role ENUM('user','admin') DEFAULT 'user' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关键兼容点:

  • 若你用MySQL 8.0+,默认SQL模式含ONLY_FULL_GROUP_BY,而项目中部分查询(如SELECT name, COUNT(*) FROM orders GROUP BY user_id)未严格遵循,会导致java.sql.SQLException: Expression #1 of SELECT list is not in GROUP BY clause。临时关闭:SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));
  • VARCHAR(50)存密码明文(血泪经验:这是教学项目,不是生产代码,但你必须知道它有多危险);
  • 字符集必须为utf8mb4,否则中文商品名存入后变??,且web.xml中<jsp-config>的page-encoding要同步设为UTF-8。

2.3 Tomcat部署三步法:war包生成与context-path控制

项目无构建工具,所以不能mvn package。你需要手工打包成WAR:

# 进入项目根目录(即含WebContent的目录) cd /path/to/javaweb_huawei_mall # 创建war包(Linux/macOS用zip,Windows可用7-Zip或PowerShell Compress-Archive) zip -r huawei_mall.war WebContent/* # 或Windows PowerShell(管理员权限) Compress-Archive -Path WebContent -DestinationPath huawei_mall.war

然后将huawei_mall.war丢进Tomcat的webapps/目录。不要解压——Tomcat会自动解压。启动Tomcat后,访问http://localhost:8080/huawei_mall/(注意路径名来自war包名,非项目名)。若首页空白或404,检查Tomcat日志logs/catalina.out中是否有SEVERE: Error filterStart—— 这90%是WEB-INF/lib/里缺jar包。

参数说明:web.xml中<welcome-file-list>定义了默认首页,通常是index.jsp;<servlet-mapping>将/loginURL映射到LoginServlet.class;<filter>如EncodingFilter负责统一请求编码,其<init-param>中的encoding值必须与JSP页面<%@ page contentType="text/html;charset=UTF-8" %>一致,否则中文参数乱码。


3. 核心业务链路拆解:从登录Filter到订单Servlet的7层调用栈

这个项目的价值不在功能多炫,而在它用最朴素的方式暴露了Java Web的“毛细血管”。我们以一次完整登录为例,追踪从浏览器输入URL到跳转成功页面的全过程。这不是理论,是你打断点后真能看到的调用栈。

3.1 请求入口:Filter链如何劫持并预处理HTTP请求

项目通常包含至少两个Filter:EncodingFilter(解决GET/POST中文乱码)和LoginFilter(拦截未登录用户访问后台页面)。它们在web.xml中按声明顺序执行:

<filter> <filter-name>EncodingFilter</filter-name> <filter-class>com.huawei.mall.filter.EncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>LoginFilter</filter-name> <filter-class>com.huawei.mall.filter.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/admin/*</url-pattern> <!-- 只拦截/admin/下的所有请求 --> </filter-mapping>

EncodingFilter.doFilter()核心逻辑:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // POST请求必须在getParameter()前设置编码 if ("POST".equalsIgnoreCase(request.getMethod())) { request.setCharacterEncoding(encoding); // 关键!放这里 } response.setContentType("text/html;charset=" + encoding); chain.doFilter(request, response); // 放行给下一个Filter或Servlet }

为什么必须放这里?因为request.getParameter()第一次调用时,Tomcat会根据当前编码解析表单数据并缓存结果。如果setCharacterEncoding()写在chain.doFilter()之后,就失效了——这是新手翻车最多的地方。

3.2 Servlet响应:LoginServlet如何完成身份校验与状态管理

LoginServlet是整个认证流程的中枢。它接收login.jsp提交的表单,执行以下操作:

  1. 获取参数:String username = request.getParameter("username");
  2. DAO查询:调用UserDao.findByUsername(username),该方法通过JDBCUtils.getConnection()获取连接,执行SELECT * FROM users WHERE username=?;
  3. 密码比对:项目用明文存储,所以直接if (user.getPassword().equals(password))(再次强调:仅教学,切勿模仿);
  4. Session写入:HttpSession session = request.getSession(); session.setAttribute("user", user);
  5. 重定向:response.sendRedirect(request.getContextPath() + "/index.jsp");

关键细节:

  • request.getContextPath()返回应用上下文路径(如/huawei_mall),避免硬编码;
  • getSession()若session不存在会自动创建,但getSession(false)则只获取不创建,用于判断是否已登录;
  • sendRedirect()是客户端跳转,URL会变;request.getRequestDispatcher().forward()是服务器端跳转,URL不变,但request域属性可传递。

3.3 JSP渲染:EL表达式与JSTL如何安全输出动态数据

index.jsp中常见代码:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:if test="${empty sessionScope.user}"> <a href="${pageContext.request.contextPath}/login.jsp">请登录</a> </c:if> <c:if test="${not empty sessionScope.user}"> 欢迎,${sessionScope.user.username}! <a href="${pageContext.request.contextPath}/logout">退出</a> </c:if>

避坑点:

  • ${sessionScope.user.username}中sessionScope明确指定作用域,避免EL表达式在page/request/session/application中逐级查找导致意外覆盖;
  • <c:if>标签需引入JSTL库(jstl-1.2.jar和standard-1.1.2.jar必须在WEB-INF/lib/中),否则报org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved;
  • pageContext.request.contextPath比硬写/huawei_mall更健壮,适配不同部署路径。

4. 避坑指南:5个让90%新手卡住的致命细节与修复方案

这个项目看似简单,但因其年代久远、环境敏感、文档缺失,实际部署时踩坑率极高。以下是我在带教23届实习生时统计出的TOP5高频问题,每一条都附带现象、根因和可立即执行的修复命令。

4.1 现象:启动Tomcat后访问/huawei_mall/login.jsp显示HTTP Status 404

原因:web.xml中<welcome-file-list>未包含login.jsp,且URL未匹配任何Servlet映射;或WebContent目录未被正确识别为Web Root。
解决:

  1. 检查web.xml是否有<welcome-file>login.jsp</welcome-file>;
  2. 在IDEA中右键项目 →Open Module Settings→Project Settings→Modules→ 选中项目 →Sources标签页 → 将WebContent目录标记为Resources,再切换到Paths标签页 →Web Resource Directories→ 点+添加WebContent,并设Web Resource Directory为WebContent,Web.xml路径为WebContent/WEB-INF/web.xml。

4.2 现象:登录时用户名密码正确,但跳转回登录页,控制台无报错

原因:LoginServlet中response.sendRedirect()的路径错误,或web.xml中<servlet-mapping>的<url-pattern>与form表单action不匹配。
解决:

  1. 查看login.jsp中<form action="login">,确认action值;
  2. 查看web.xml中LoginServlet的<url-pattern>,必须完全一致(如/login);
  3. sendRedirect()中路径必须带上下文路径:response.sendRedirect(request.getContextPath() + "/index.jsp");,不能写"/index.jsp"(这是绝对路径,会跳到Tomcat根目录)。

4.3 现象:中文用户名存入数据库后变成??,或页面显示某某乱码

原因:MySQL连接URL未指定字符集,或JDBC驱动版本与MySQL不匹配。
解决:

  1. 修改JDBCUtils.java中getConnection()方法的URL:
    private static final String URL = "jdbc:mysql://localhost:3306/huawei_mall?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8";
  2. 确保使用mysql-connector-java-5.1.47.jar(兼容MySQL 5.7)或8.0.28.jar(兼容MySQL 8.0),绝不能混用;
  3. 检查MySQL服务端配置my.cnf中[client]和[mysqld]段均有default-character-set = utf8mb4。

4.4 现象:点击“添加到购物车”报java.lang.ClassNotFoundException: com.huawei.mall.dao.CartDao

原因:src/下Java文件未编译,或编译后class文件未输出到WEB-INF/classes/目录。
解决:

  1. 手动编译:javac -d WebContent/WEB-INF/classes -sourcepath src -cp "WebContent/WEB-INF/lib/*" src/com/huawei/mall/servlet/*.java src/com/huawei/mall/dao/*.java src/com/huawei/mall/filter/*.java;
  2. 或在IDEA中:Build→Build Project,然后检查out/production/javaweb_huawei_mall/(或target/classes/)是否生成了对应class文件,并确认WebContent/WEB-INF/classes/已同步(IDEA需勾选Build→Compiler→Build project automatically)。

4.5 现象:LoginFilter拦截/admin/*失败,未登录也能访问/admin/userList.jsp

原因:web.xml中<filter-mapping>的<url-pattern>写成了/admin/*,但实际请求URL是/huawei_mall/admin/userList.jsp,而Filter匹配的是相对于上下文路径的路径,即/admin/*匹配/admin/xxx,不匹配/huawei_mall/admin/xxx。
解决:

  1. web.xml中<filter-mapping>保持<url-pattern>/admin/*</url-pattern>;
  2. 确保所有/admin/开头的页面链接都用相对路径:<a href="${pageContext.request.contextPath}/admin/userList.jsp">用户管理</a>;
  3. 或修改Filter逻辑,在doFilter()中用request.getServletPath()获取真实路径并截取。

5. 安全加固实操:3个零成本补丁让教学项目具备生产级防御雏形

别被“教学项目”四个字骗了。当你把javaweb华为商城.zip部署到公司内网供新人练手时,一个未过滤的<%= request.getParameter("keyword") %>就能让XSS漏洞裸奔。下面三个补丁,无需改架构、不加新框架,纯Java代码级加固,亲测可防90%扫描器误报。

5.1 XSS防御:全局HTML转义Filter(替代JSP中手写<c:out value="${xxx}"/>)

在现有EncodingFilter后追加一个XssFilter,对所有响应输出做HTML实体转义:

// com.huawei.mall.filter.XssFilter.java public class XssFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) resp; // 包装response,重写getWriter()方法 XssResponseWrapper wrapper = new XssResponseWrapper(response); chain.doFilter(req, wrapper); } } // XssResponseWrapper.java(继承HttpServletResponseWrapper) public class XssResponseWrapper extends HttpServletResponseWrapper { public XssResponseWrapper(HttpServletResponse response) { super(response); } @Override public PrintWriter getWriter() throws IOException { return new HtmlEscapePrintWriter(super.getWriter()); } } // HtmlEscapePrintWriter.java(继承PrintWriter) public class HtmlEscapePrintWriter extends PrintWriter { public HtmlEscapePrintWriter(Writer out) { super(out, true); } @Override public void write(String s) { if (s != null) { super.write(StringEscapeUtils.escapeHtml4(s)); // 使用commons-text 1.10.0 } } }

部署:将commons-text-1.10.0.jar放入WEB-INF/lib/,并在web.xml中<filter-mapping>置于EncodingFilter之后。这样,所有out.print()输出的字符串都会自动转义<为&lt;,>为&gt;,彻底阻断反射型XSS。

5.2 SQL注入防御:PreparedStatement强制替换所有Statement

项目中所有DAO方法几乎都用Statement拼接SQL,这是SQL注入温床。只需两步替换:

  1. 找到所有new Statement()和executeQuery("select * from users where username='"+username+"'");
  2. 统一改为PreparedStatement:
// 替换前(危险!) String sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 替换后(安全) String sql = "SELECT * FROM users WHERE username=? AND password=?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password); ResultSet rs = pstmt.executeQuery();

技巧:用IDEA的Replace in Path(Ctrl+Shift+R),搜索"SELECT.*FROM.*WHERE.*'\\+.*\\+'",替换为PreparedStatement模板,效率提升10倍。

5.3 密码安全升级:BCrypt加密替代明文存储(兼容老数据库字段)

users.password字段长度是VARCHAR(50),刚好够存BCrypt哈希值(60字符)。改造步骤:

  1. 引入bcrypt库:jbcrypt-0.4.jar放入WEB-INF/lib/;
  2. 修改注册逻辑:
    String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt(12)); // 插入hashedPassword而非明文
  3. 修改登录校验:
    if (BCrypt.checkpw(inputPassword, dbPassword)) { // dbPassword是数据库查出的哈希值 // 登录成功 }

血泪经验:BCrypt.gensalt(12)中12是log rounds,值越大越安全但越慢,12是平衡点;BCrypt.checkpw()内部已处理盐值提取,无需额外存储salt字段。


6. 从教学项目到工程能力:用这个zip包反向训练你的Java Web底层直觉

我带过的实习生里,有两人后来成了团队主力后端。他们做对了一件事:把javaweb华为商城.zip当“解剖标本”,而不是“交作业的壳”。他们不满足于跑通,而是用它验证每一个模糊概念——比如,当学到“Cookie和Session区别”时,他们关掉浏览器Cookie,看Session是否还生效;当学到“Filter生命周期”,他们给init()方法加System.out.println,观察Tomcat启动时Filter是否真被加载一次;当学到“JDBC连接池”,他们把JDBCUtils.getConnection()改成HikariCP,对比QPS变化。这种“破坏性验证”,才是这个老项目真正的价值。

我自己的习惯是:每次接手一个新框架(比如Spring Security),第一件事就是用这个商城项目做对照实验。比如,我把LoginFilter的逻辑,一行行翻译成Spring Security的SecurityConfig配置,看http.authorizeRequests()如何替代URL Pattern匹配,看AuthenticationManager如何替代UserDao查询。这种“旧世界→新世界”的映射,比读十篇原理文章都管用。

另一个实用技巧:把这个项目当成“协议探测器”。你怀疑某个HTTP Header(如X-Forwarded-For)被Nginx透传失败?把它加到LoginServlet里打印出来;你搞不清request.getRemoteAddr()和request.getRemoteHost()区别?在EncodingFilter里同时打出来对比。它足够轻、足够脏、足够透明,没有任何魔法,所有变量都在你眼皮底下流动。

最后说一句实在话:现在招人,面试官早不问“你做过什么项目”,而是问“你在这个项目里,删过哪行代码?为什么删?删了之后发生了什么?”。如果你能指着javaweb华为商城.zip里某行out.print(request.getParameter("xxx"))说:“我把它删了,加了XSS Filter,因为……”,那你已经跨过了初级和中级的那道墙。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询