
简介面向Java Web课程设计或毕业设计的网商购物网站完整项目适合计算机相关专业学生、教师及入门开发者用于项目实践、课堂作业与二次开发。资源共79个文件压缩包约730KB主要包含JSP页面、Java源码、XML配置、CSS样式与JavaScript脚本等类型前端交互、后端逻辑和工程配置均覆盖并附带详细文档与项目授权码便于快速部署与功能扩展。项目源自高分通过的答辩源码经测试运行成功功能可靠可作为课程设计、毕业设计或电商系统初期的参考框架。目前已有46人学习浏览适合希望系统了解Java Web购物网站模块设计、提升开发流程认知的读者。1. 为什么网商购物网站至今仍是 Java Web 课程设计最稳的考题网商购物网站之所以在 Java Web 课程设计里经久不衰不是因为它新颖而是因为它把 Web 开发的核心考点全部串在了一起HTTP 请求生命周期、Servlet 或 Spring MVC 的请求映射、JDBC 与 MyBatis 的数据访问、Session 与 Cookie 的会话保持、数据库事务的一致性、前端表单与 AJAX 的交互。让你一口气把这些都练过答辩时每一个模块都能展开讲出两三页的原理课程设计评分自然不会低。但大多数人卡在两个地方一是不知道怎么定技术选型明明能跑的代码为什么换台机器就打不开二是拿到了别人的项目包却改不动、讲不清、答不出。这篇文章按我多年带课设的真实思路把从零搭一个可讲、可改、可部署的网商购物网站的完整路径讲清楚连部署命令和答辩追问都给你准备好。适合刚学完 Servlet 和 JSP 的在校学生也适合想用半天时间把旧项目重构出清爽结构的工作开发者。2. 技术栈与工程结构SSM、Spring Boot 还是 JSP 直连2.1 三种常用选型怎么选做过课设的人都知道网上能下载到的项目大致分成三代。第一代是纯 JSP Servlet JDBC页面里写 Java 代码数据源用硬编码的 JDBC 驱动胜在结构直白、老师一眼能看懂但复用性和维护性很差。第二代是 SSM 即 Spring Spring MVC MyBatis最常见也是大多数高分项目的标配。第三代是 Spring Boot MyBatis Plus Thymeleaf 或 Vue启动直接一个 main 方法内置 Tomcat部署成本最低。三者的对比我列在下面。技术路线学习成本答辩深度换环境部署难度企业认可度JSPServletJDBC低中中要手动配 Tomcat低SSM中高中中Spring Boot低高低高如果你是刚入门SSM 是最推荐的方案因为面试题里问的 IOC、AOP、MyBatis 动态代理全都来自这套体系。如果项目是从网上下载的压箱底老包通常是 JSP 版本建议只保留它的页面和数据库设计文档业务层重新写一遍这样既避开了老代码里的乱结构答辩时还能理直气壮说是自己重构过的。2.2 Maven 工程目录设计拿到压缩包先看后缀名Spring Boot 的包通常是.jarSSM 是.war为了让老师能在自己的机器上跑起来war 包要保持标准的 Maven 结构。我习惯用 maven-archetype-webapp 骨架创建工程然后手动补目录最终结构如下net-mall/ ├── pom.xml ├── src/main/java │ └── com/example/mall/ │ ├── controller/ // 前端控制器只做参数接收和跳转 │ ├── service/ // 业务层事务在这层控制 │ ├── dao/ // MyBatis Mapper 接口 │ ├── entity/ // 实体类和数据库表对应 │ ├── common/ // 全局返回结果、异常、工具类 │ └── config/ // 拦截器、WebMvc 配置 ├── src/main/resources │ ├── mybatis/ // mapper.xml 文件 │ ├── static/ // js、css、图片 │ ├── templates/ // jsp 或 thymeleaf 页面 │ └── application.properties └── src/main/webapp/WEB-INF/ // 放 jsp 文件时使用pom.xml里有几个依赖必须锁死版本Servlet API 用 provided 范围MyBatis 和 Spring 的版本要一致如果用了 JDK 8 就不要去追 Spring 6。常见错误是拷贝网上别人的 pom.xml依赖版本相互冲突启动时报NoSuchMethodError。遇到这种问题先执行mvn dependency:tree把冲突的依赖用 exclude 排除掉。2.3 前后端交互方式决策购物网站最核心的页面是商品列表、购物车、确认订单这三个页面需要频繁刷新局部数据。如果你用 JSP传统做法是request.setAttribute然后request.getRequestDispatcher().forward()整页刷新。如果你想用 AJAX 加分建议单独写一个ResponseBody的接口返回统一 JSON 结构。我一般会在 common 包里写一个 Result 类public class ResultT { private Integer code; // 200 成功401 未登录500 异常 private String message; // 给用户看的提示信息 private T data; // 业务数据 public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.message ok; r.data data; return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.code 500; r.message msg; return r; } }这段代码返回给前端的是统一结构前端 JS 里只需要判断res.code就能知道接口是否正常不需要每个页面单独处理奇怪的错误文案。购物车的加减商品数量、移除商品、结算时检查库存这三个接口都返回 Result前端拿到 code 后再决定是弹窗还是刷新。3. 数据库建模与购物车设计四张核心表与订单状态机3.1 实体关系梳理网商购物网站的数据库设计是答辩时最容易拿分的点千万不能全表关联到一堆没用的外键。真正的核心只有四张表用户表 user、商品表 product、购物车表 cart、订单表 orders再加上一个订单明细表 order_item。用户和商品是多对多关系由订单作为桥梁。购物车表里不存商品快照只存 sessionId 或 userId、商品 id、数量。订单表必须冗余一份商品快照信息否则未来商品改价会导致历史订单金额对不上这是课设答辩时老师最喜欢追问的地方之一。我见过很多下载的项目把这些表揉成七八张还加了管理员表、分类表、轮播图表但实际上分类字段放商品表里用 int 类型就能解决没必要单独建表。数据库设计原则是上课设的项目宁可表少而精也不要用一堆你用不上的关联表展示存在感。3.2 建表 SQL 与字段说明假设 MySQL 8.0字符集默认 utf8mb4下面是关键建表语句我在注释里标好了每段 SQL 对应的场景考试照抄这个结构基本不会被扣分-- 用户表不存明文密码至少用 MD5 加盐答辩可以说这是为上线做的准备 CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 自增主键, username varchar(50) NOT NULL COMMENT 登录名唯一, password varchar(64) NOT NULL COMMENT 加盐后的密文长度要预留 32 位以上, salt varchar(16) DEFAULT NULL COMMENT 盐值, phone varchar(20) DEFAULT NULL COMMENT 手机号不参与业务逻辑, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 商品表重点确认 state 字段下架商品不用 delete用逻辑删除 CREATE TABLE product ( id bigint NOT NULL AUTO_INCREMENT, name varchar(120) NOT NULL COMMENT 商品标题, price decimal(10,2) NOT NULL COMMENT 售价用 decimal 不用 double, stock int NOT NULL DEFAULT 0 COMMENT 库存, image_url varchar(255) DEFAULT NULL, state tinyint NOT NULL DEFAULT 1 COMMENT 1 上架 0 下架, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 购物车表user_id 和 product_id 加联合唯一索引防止同一个商品插入两行 CREATE TABLE cart ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, product_id bigint NOT NULL, quantity int NOT NULL DEFAULT 1 COMMENT 数量, PRIMARY KEY (id), UNIQUE KEY uk_user_product (user_id, product_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单状态用 tinyint0 待支付 1 已支付 2 已发货 3 已完成 4 已取消 CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号前端展示用, user_id bigint NOT NULL, total_price decimal(10,2) NOT NULL, receiver_name varchar(50) NOT NULL, receiver_phone varchar(20) NOT NULL, receiver_address varchar(255) NOT NULL, state tinyint NOT NULL DEFAULT 0, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;这段 SQL 中uk_user_product联合唯一索引是购物车表的关键没有它就会出现同一个用户同一个商品在数据库里存了两条记录的脏数据。price 用decimal(10,2)而不是 double是因为 double 在金额比较时会有精度误差答辩时能说出来“金额浮点精度的踩坑”是一个比较大的加分项。3.3 订单状态机的 5 个状态下单后订单状态的流转建议单独抽成一个枚举类页面和接口都引用这个枚举避免硬编码魔法数字。在 service 层判断状态转移时例如“已支付”的订单不能被取消就是判断当前 state 是否等于枚举里的某个值。我用一个常量类或枚举来管理public enum OrderState { // 待支付、已支付、已发货、已完成、已取消 UNPAID(0), PAID(1), SHIPPED(2), COMPLETED(3), CANCELED(4); private final int state; OrderState(int state) { this.state state; } public int getState() { return state; } }实际开发中有一个很典型的问题用户在支付页停留了很久期间商品价格已经变化下单金额和当前价格不匹配此时是否允许支付课设里你只要回答“下单时已经把金额快照进订单表所以按订单金额收款这是正常业务逻辑”就够用了。时间不充足的情况下不需要实现订单自动过期只要在订单列表页里放一个“取消订单”按钮用前端定时器刷新状态即可。4. 核心模块落地方案登录态、购物车、下订单与定时关单4.1 登录态与全局拦截器购物网站首先要有登录写登录接口时不只要检查账号密码还需要做一个拦截器不让没登录的人跳转购物车或订单页面。这里的实现方式很常规用户登录成功后把 user 对象放进 session同时写一个 HandlerInterceptor 校验 session 里是否存在用户。SSM 项目在 spring-mvc.xml 里注册拦截器Spring Boot 项目用 Java Config 注册。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { // 这里是 AJAX 请求和普通页面请求的区别判断 String accept request.getHeader(Accept); if (accept ! null accept.contains(application/json)) { response.setContentType(application/json;charsetutf-8); response.getWriter().write({\code\:401,\message\:\未登录\}); } else { response.sendRedirect(request.getContextPath() /login.html); } return false; } return true; } }这段代码里最值得注意的地方是区分了 AJAX 请求和普通页面跳转。如果网上参考项目没有这一步你把他漏掉的逻辑补上直接在课程设计答辩中解释“未登录的用户点购物车按钮时页面不跳转而是弹出登录框”评委会觉得你考虑了交互细节。登录密码这一步按课程设计级别是用 MD5 加盐用DigestUtils.md5DigestAsHex((password salt).getBytes())后入库不能直接存明文。4.2 购物车按用户维度存库购物车有两种做法。一种是把 cart 数据存 Session 或 Cookie优点是不登录也能加购缺点是换浏览器就丢、Cookie 有大小限制。另一种是存数据库按创建订单的接口来写也就是登录以后把购物车绑定到 userId。我推荐第二种因为在课程设计评分里“用户重新登录购物车数据还在”是一个明显的加分功能。加入购物车接口的逻辑是先按 userId productId 查询 cart 表如果已存在就执行update quantity quantity 1不存在就执行 insert。这里可以用一个INSERT ... ON DUPLICATE KEY UPDATE quantity quantity 1简化成一条 SQL前提是前面建表时的联合唯一索引必须在。购物车展示列表时要做联表查询因为购物车表里只有 product 的 id需要 join product 表查出商品名、价格、图片SELECT c.id, c.product_id, c.quantity, p.name, p.price, p.image_url FROM cart c LEFT JOIN product p ON c.product_id p.id WHERE c.user_id #{userId}这里我特意用 LEFT JOIN 而不是 INNER JOIN是为了防止商品被删后购物车整条记录消失。联表查询后在 Java 里把结果包装成一个 CartVO 对象页面里直接循环这个 VO比在 JSP 里反复c:if判断空值要清爽得多。4.3 下单事务中先锁库存还是先减库存下单是整个项目里最封神的一段逻辑。需要完成的动作包括检查商品库存是否足够、扣减库存、生成订单主表记录、生成订单明细表记录、清空购物车。这五个操作要么全成功要么全部回滚所以必须用事务。先减库存还是先检查库存和订单一般是 select stock 判断如果库存大于等于数量再 update stock。但注意两个用户同时下单同一件商品都查到了库存 10同时去扣就可能发生超卖。课程设计里评委不会真给你并发压测但你要能把超卖的原理讲出来。最稳妥的方案是使用数据库的行锁也就是把 update 语句放在查询之前来触发行锁或者用乐观锁在 update 时加条件stock quantity影响行数为 0 则抛异常回滚。我最终落地用的是条件更新Transactional(rollbackFor Exception.class) public void createOrder(Long userId, Long productId, int quantity) { Product product productMapper.selectByIdForUpdate(productId); if (product.getStock() quantity) { throw new ServiceException(库存不足); } int affected productMapper.reduceStock(productId, quantity); if (affected 0) { throw new ServiceException(库存不足请刷新重试); } // 生成订单表记录 Order order buildOrder(product, quantity); orderMapper.insert(order); // 生成订单明细 orderItemMapper.insert(buildOrderItem(order.getId(), product, quantity)); // 清空购物车 cartMapper.deleteByUserIdAndProductId(userId, productId); }Transactional注解中rollbackFor Exception.class必须写因为默认情况下 Spring 只对 RuntimeException 回滚如果你的 ServiceException 继承的是 Exception 就不会触发回滚。到时候你遇到“库存扣了但订单没生成”的问题十有八九是这里漏了。reduceStock对应的 SQL 是update product set stock stock - #{quantity} where id #{id} and stock #{quantity}通过受影响行数判断失败这比 select 再 update 更抗并发。4.4 支付模拟与回调支付模块不需要真的对接支付宝微信但接口结构和回调流程一定要完整。前端点击“去支付”后后端生成一个支付单把订单号传入一个模拟支付页面支付页面显示“模拟支付成功”按钮。点击后调用/payment/callback接口这个接口的作用是修改订单状态为已支付。PostMapping(/payment/callback) public ResultString payCallback(RequestParam String orderNo, RequestParam String payNo) { Order order orderMapper.selectByOrderNo(orderNo); if (order null) { return Result.error(订单不存在); } if (order.getState() OrderState.PAID.getState()) { return Result.success(重复回调已幂等); } orderMapper.updateState(order.getId(), OrderState.PAID.getState()); // 这里可以缩略掉记录支付流水表 return Result.success(支付成功); }这段代码里把“重复回调不重复修改订单状态”做了简单的幂等判断也就是先查订单状态如果是已支付就直接返回成功。这个知识点答辩时提一句“防止网络抖动导致重复回调”会显得你不只是在交作业。真正上线前还需要校验签名课设阶段在回调里加一个 sign 字段并用 MD5 简单验证即可。5. 从“能跑”到“高分”验收清单、部署打包与答辩追问5.1 一张功能自查表课程设计评分通常不只看功能还看易用性和健壮性。我把常见的老师验证点整理成一个自查表每完成一项打个勾这也是你写“项目文档”时的目录来源。功能模块必须能演示的操作常见扣分点注册登录注册后能直接登录密码明文存储、无重复名校验商品列表分页、点击商品进详情一整页所有商品全部查出购物车加购、修改数量、删除刷新后购物车丢失确认订单填收货地址、展示金额总价与明细相加不一致支付模拟支付、状态变已支付刷新后回到待支付订单列表不同状态分类显示订单状态硬编码数字5.2 本机部署和导出 war 包如果你的项目是 SSM导出 war 包后需要通过 Maven 的 package 命令打成 war。如果班级里统一使用 IntelliJ IDEA直接执行mvn clean packagetarget 目录下生成xxx.war把名字改成ROOT.war扔进 Tomcat 的 webapps 目录启动 Tomcat 后用http://localhost:8080/访问。Spring Boot 项目就简单得多先确认application.properties中数据库地址、用户名、密码都对再执行mvn package生产 java -jar 命令启动。# 打包并跳过测试课程设计阶段测试类会拖慢时间 mvn clean package -DskipTests # Spring Boot 后台运行方式 nohup java -jar target/net-mall-1.0.0.jar --server.port8080 app.log 21 # 查看启动日志是否有异常 tail -f app.log这里的-DskipTests是跳过测试代码但保留编译 app.log 21是把标准输出和错误输出都重定向到日志文件。有同学反映“项目在 IDEA 里能跑打成 jar 就不能启动”一般原因是 resources 里配置文件的密码还是本地数据库的换成服务器的数据库地址就好。另一个常见问题是用 Tomcat 10 跑 SSM 项目报错ClassNotFoundException: javax.servlet.*需要在 pom.xml 里确认依赖的是jakarta.servlet-api或换回 Tomcat 9。5.3 答辩时老师常问的底层问题答辩时间通常只有 5 到 10 分钟回答问题时不要背概念直接说代码里怎么做的。老师问“事务怎么控制的”你就说在 service 的createOrder方法上加了Transactional(rollbackFor Exception.class)随后解释库存扣减失败会抛异常触发回滚同时用条件 update 防止超卖。老师问“MyBatis 和 JDBC 的区别”你直接说 mapper 接口是动态代理生成的SQL 写在 XML 里便于维护自带参数映射和结果集映射。如果老师追问 Session 和 Cookie 的区别你就结合购物网站讲登录后把 user 对象放 Session浏览器端拿到 JSessionId 的 Cookie下次请求带上它服务器就能识别用户。如果追问登录状态保存在服务端有什么缺点你可以回答“多台服务器时要引入 Redis 做共享 Session”这样不但没露怯还展示了你了解分布式会话。不需要紧张课程设计的答辩本质上就是看你对自己代码的熟悉程度只要每个接口你能说出“参数从哪里来、数据在哪个表、状态怎么变”评分基本就稳了。5.4 让课设变成简历项目的一处小改造很多人做完课设就丢其实只要在最后做一个改动这个项目就能填进简历的“项目经历”栏。改动内容是给订单模块加一个定时关闭订单的逻辑下单后 30 分钟未支付就自动取消库存回滚。Spring Boot 里用Scheduled注解标一个定时方法每分钟扫一次create_time超过 30 分钟、状态为待支付的订单改成取消并加回库存。SSM 项目则需要整合 Quartz代码稍多但原理一致。这一步投入的改造时间大约两个小时却能让项目从“基本功能都有”升级成“有真实业务复杂度”。写简历时项目描述能写出一句“通过定时任务实现订单超时自动取消与库存回滚保证数据最终一致性”比写十个“实现了增删改查”都更有说服力。到这里项目已经完成了九成剩下的一成是把答辩 PPT 截几张核心页面图标注清楚数据库表关系就可以去验收了。本文还有配套的精品资源点击获取