news 2026/10/7 3:00:53

SSM+JSP智慧商城毕业设计:从数据库到部署全流程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM+JSP智慧商城毕业设计:从数据库到部署全流程实战

毕业设计选商城这类题目的人,这两年真是越来越多。理由很简单:商城项目覆盖了电商系统最常见的业务链路,从用户注册登录、商品浏览、购物车到下单选品,每一步都能对应到 Servlet、JSP、SSM 框架里的一套典型写法,工作量足够、答辩又好讲。我见过不少同学拿着“智慧商城平台”这个题目来找我,用的就是 JSP + JavaWeb + SSM 这套技术栈,最后做出一个能跑通全流程、界面还过得去的完整项目。这篇就把我从零搭这样一个项目的过程、踩过的坑、以及最后答辩时被问得最多的点,全部写出来。

先说清楚这篇东西适合谁:你如果正在做类似题目,对 JSP、Servlet、SpringMVC、MyBatis 有基础但还没串起来,那你照着捋一遍基本能有底。如果你只会 Spring Boot、没碰过传统 war 包部署,那这篇还能帮你补上“JavaWeb 项目在 IDEA 里到底怎么配 Tomcat、怎么打包”这块实操短板。

1. 项目整体定位与功能梳理

1.1 为什么毕业设计要选商城而不是简单管理系统

很多教程里的图书管理、学生信息管理系统看起来简单,但正因为简单,里里外外都是单表 CRUD,答辩时展示内容少,老师一问事务、并发、表设计就露怯。商城项目不一样,它天然具备“用户 - 商品 - 购物车 - 订单 - 支付 - 物流状态”这样的业务纵深,哪怕你只做雏形,也能把需求分析、数据库设计、模块拆解写得非常饱满。

更重要的是商城几乎所有功能都在“用户是否登录”这个前提下运作,这逼着你认真处理 Session、拦截器、Cookie、权限控制。这些内容单独拿出来写“技术难点”都说得通。你要做的不是追求页面多华丽,而是把这条链路跑通、把核心业务逻辑写严谨。这恰恰是毕业设计最看重的。

1.2 角色与功能模块如何拆才合理

智慧商城平台按使用角色分,至少要有两个端:前台用户端和后台管理端。前台的用户流程是注册、登录、首页浏览、商品分类查看、搜索、商品详情、加入购物车、去结算、生成订单、查看个人中心;后台则需要商品管理、分类管理、订单管理、用户管理、轮播图管理、公告管理。两者共用同一套数据库,只是页面和接口权限不同。

一般我会建议功能模块拆成下面这个表,这样无论是画功能结构图还是写开题报告,都很直观:

  • 用户模块:注册、登录、退出、个人信息查看与修改、密码修改、头像上传
  • 商品模块:商品列表分页、按分类筛选、按关键字搜索、商品详情、库存显示
  • 购物车模块:加入购物车、修改数量、删除商品、勾选商品、批量结算
  • 订单模块:确认订单、生成订单、支付模拟、订单列表、订单状态变更、取消订单
  • 后台模块:管理员登录、商品上下架、分类维护、订单发货、用户管理

这个模块划分核心原则是“一个模块只做一件事”,后续写 Controller、写 Mapper 映射文件的时候,按模块建包,代码管理会舒服很多。

1.3 技术选型:为什么是 SSM 而不是 Spring Boot

先说结论:不是 Spring Boot 不行,而是对大部分毕设场景来说,SSM 的传统分层更符合课程大纲。SSM 是 Spring + SpringMVC + MyBatis 的组合,Spring 管对象、SpringMVC 管请求分发、MyBatis 管数据持久化。它把 MVC 分层结构体现得非常清晰,非常适合在答辩时讲“为什么这么设计”。

另外很多学校课程还在教 JSP,你直接沿用到项目里,工程上最合理、也最容易过查重。JSP + JSTL 在服务端渲染数据,不需要额外搭前端工程,省掉 Vue 环境配置、跨域处理这些额外问题。当然你会觉得 JSP 写起来笨重,可这种笨重在毕业设计中反而是优势,因为更容易用、更可控,面试官或者答辩老师一看就懂。

还有一个现实问题:如果导师明确要求 SSM 框架,那 Spring Boot 方案即便技术上更先进也可能被否。选技术栈第一优先是看题目要求和教学导向,不是看谁更时髦。

2. 数据库设计:决定项目上限的地基

2.1 核心表拆成七张才够用

商城数据库表数量没有绝对标准,但我个人建议至少七张:用户表 t_user、分类表 t_category、商品表 t_product、购物车表 t_cart、订单表 t_order、订单明细表 t_order_item、公告表 t_notice。管理员表可以和用户表合并,加一个 role 字段区分,或者单独建 t_admin,看个人习惯。轮播图可以单独建表,也可以直接在后台配置页用一张配置表存图片路径。

每张表的字段,我是按这个逻辑设计的:

用户表 t_user:user_id 主键自增、username 用户名唯一、password 密码字段存 MD5 加盐后的值、nickname 昵称、phone 手机号、email 邮箱、avatar 头像地址、create_time 创建时间、status 状态(0 正常 1 禁用)。

分类表 t_category:category_id、category_name、parent_id(支持二级分类)、sort_order 排序号。

商品表 t_product:product_id、category_id、product_name、subtitle 副标题、main_image 主图、detail 商品详情(富文本内容)、price 价格、stock 库存数量、sales 销量、status 上架状态、create_time。

购物车表 t_cart:cart_id、user_id、product_id、quantity 数量、checked 是否勾选(1 勾选 0 未勾选)。这里加 checked 字段主要是为了方便“只结算勾选商品”的设计,后面订单生成只用它。

订单表 t_order:order_id、order_no 订单编号、user_id、total_amount 总金额、status 状态码、receiver_name、receiver_phone、receiver_address、create_time、pay_time。订单编号建议用时间戳加随机数,别用自增 id 暴露订单量。

订单明细表 t_order_item:item_id、order_id、product_id、product_name、product_image、current_price、quantity、total_price。这里必须存一份商品名称和价格的快照,因为商品信息后续可能改价、可能下架,而用户的订单历史不能被改动影响。

2.2 字段设计中的几个关键选择和陷阱

价格字段强烈推荐用 decimal(10,2),不要用 float 或 double。float 是二进制浮点数,算金额容易出 0.30000000000000004 这种精度问题,答辩时老师问到金额精度你能答上来就是加分项。decimal 是定点数,存储和计算都不会出现精度丢失,唯一注意点是运算要保证两位小数。

库存和销量字段建议设置默认值 0,并且库存字段加一个无符号约束。扣库存的时候最好用带条件的 UPDATE 语句:UPDATE t_product SET stock = stock - #{quantity} WHERE product_id = #{productId} AND stock >= #{quantity}。这样可以避免超卖,这个我在后面下单部分会细说。

时间字段统一用 datetime,不要图省事用 varchar 存日期字符串。虽然 varchar 显示方便,但用 ORDER BY create_time 排序列的时候字符串排序会出问题,比如 2024-01-01 12:00:00 排在 2024-01-01 09:00:00 前面。

2.3 建表 SQL 片段参考

这里给出最核心的用户表和商品表建表语句,方便你快速起步:

CREATE TABLE t_user ( user_id INT NOT NULL AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(50) NOT NULL COMMENT '用户名', password VARCHAR(64) NOT NULL COMMENT '登录密码MD5', nickname VARCHAR(50) DEFAULT NULL COMMENT '昵称', phone VARCHAR(20) DEFAULT NULL COMMENT '手机号', email VARCHAR(100) DEFAULT NULL COMMENT '邮箱', avatar VARCHAR(255) DEFAULT NULL COMMENT '头像路径', status TINYINT DEFAULT 1 COMMENT '1正常 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (user_id), UNIQUE KEY uk_username (username) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE t_product ( product_id INT NOT NULL AUTO_INCREMENT COMMENT '商品ID', category_id INT NOT NULL COMMENT '分类ID', product_name VARCHAR(200) NOT NULL COMMENT '商品名称', subtitle VARCHAR(255) DEFAULT NULL COMMENT '商品副标题', main_image VARCHAR(255) DEFAULT NULL COMMENT '商品主图', detail TEXT COMMENT '商品详情', price DECIMAL(10,2) NOT NULL COMMENT '价格', stock INT NOT NULL DEFAULT 0 COMMENT '库存', sales INT NOT NULL DEFAULT 0 COMMENT '销量', status TINYINT DEFAULT 1 COMMENT '1上架 0下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (product_id), KEY idx_category (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

建表时我建议用 utf8mb4 而不是 utf8,后者在 MySQL 5.5 之前的实现里存储不了 emoji 和生僻字,商品标题里经常带特殊符号,踩一次坑就明白了。另外所有表都加 InnoDB 引擎,因为项目里有事务操作(下单),MyISAM 不支持事务和行级锁,毕设虽然数据量小但也别给自己埋雷。

3. SSM 框架搭建:从空项目到能跑页面

3.1 IDEA 里创建 Maven Web 项目的一次性正确姿势

这里说的就是搜索词里大家常碰到的 “idea 运行 javaweb 项目配置”。很多人第一步就卡在项目结构上,IDEA 新建项目的时候选 Maven,然后勾选 Create from archetype 用 maven-archetype-webapp。这个骨架的坑是缺少 java 源码目录,建完之后你需要在 src/main 下手动建 java 和 resources 文件夹,再通过右键 Mark Directory as 标记为 Sources Root 和 Resources Root。

如果你嫌麻烦,也可以直接不勾选 archetype,创建一个干净的 Maven 项目,然后自己补 src/main/java、src/main/resources、src/main/webapp/WEB-INF 目录结构,再打开 Project Structure 里的 Facets 添加 Web 支持,最后填写 web.xml 路径。这种方式更清晰,也少掉 archetype 生成的旧版 web.xml 兼容问题。pom.xml 里 packaging 记得设为 war:

<packaging>war</packaging>

Tomcat 配置建议用外部下载的 Tomcat 8.5 或 9,IDEA 里 Run Configuration 新增 Tomcat Server Local,选择 Tomcat 目录,然后在 Deployment 页签把当前项目的 war exploded 加进去。Application Context 建议填 /smart,这样启动后访问地址是 http://localhost:8080/smart/,路径前缀在 JSP 页面写绝对路径时不要写死,用${pageContext.request.contextPath}。

3.2 三大配置文件如何配合才不打架

SSM 最麻烦的就是配置文件多:Spring 核心配置、SpringMVC 配置、MyBatis 配置、web.xml。但理解了分工就不乱。Spring 配置负责管 service、mapper、数据源;SpringMVC 配置负责管 controller、视图解析器、静态资源;web.xml 负责把两者装配起来启动。

我习惯拆成两个核心配置文件,一个 spring-mybatis.xml 管 Spring 和 MyBatis 整合,一个 spring-mvc.xml 管 SpringMVC。web.xml 用 ContextLoaderListener 加载 spring-mybatis.xml,用 DispatcherServlet 加载 spring-mvc.xml。

spring-mybatis.xml 的关键内容:

<context:component-scan base-package="com.shop.service" /> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/smart_mall?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.shop.mapper" /> </bean>

spring-mvc.xml 关键内容是开启注解驱动、组件扫描、视图解析器、释放静态资源:

<mvc:annotation-driven /> <context:component-scan base-package="com.shop.controller" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/" /> <property name="suffix" value=".jsp" /> </bean> <mvc:default-servlet-handler />

web.xml 里除了 DispatcherServlet 和 ContextLoaderListener,一定要加 CharacterEncodingFilter,并且 forceEncoding 设为 true,不然 JSP 页面中文百分百乱码,这也是 JavaWeb 项目最常见的坑。路径映射建议:

<servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>

这里注意/和/*的区别:/会交给 SpringMVC 处理,但不会拦截 JSP 文件;/*会把所有请求包括 JSP 都拦截,导致页面渲染乱套。当年我用错这个配置排查了一晚上,非常经典。

3.3 分页插件和依赖版本怎么选

MyBatis 用 PageHelper 做分页确实方便,一个PageHelper.startPage(pageNum, pageSize)后面跟的查询就自动带 limit,但毕设我用它反而少。原因一是 PageHelper 的 jar 包版本和 MyBatis 版本偶尔不兼容,报错你还猜不到原因;二是手写 LIMIT 也不难,无非是先查 count 再查数据,代码写两行,过程完全可控。

如果你决定手写分页,在商品 Mapper.xml 里写:

<select id="selectPage" resultType="com.shop.entity.Product"> select * from t_product <where> <if test="categoryId != null and categoryId != ''"> and category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> and product_name like concat('%', #{keyword}, '%') </if> </where> order by create_time desc limit #{offset}, #{pageSize} </select>

依赖版本方面给个稳定组合:Spring 5.x、MyBatis 3.5.x、mybatis-spring 2.x、Druid 1.2.x、mysql-connector-java 8.0.x。JSP 需要引入 javax.servlet-api(provided 范围)或者 jstl 1.2。别用太新的 Spring 6,它要求 JDK 17,很多毕业设计的电脑上装的还是 JDK 8,用 Spring 5 最保险。

4. 核心业务模块的编码实现

4.1 用户注册登录与个人信息页展示

用户模块是第一步,也是后面所有拦截器依赖的基础。注册流程走三层:Controller 接收表单参数,Service 里先校验用户名是否存在,不存在就对新密码做 MD5 加密,最后调用 Mapper 插入数据库。

密码加密不要只对密码本身做 MD5,容易撞库,最简单的方式是加盐,也就是在密码后面拼接一个固定字符串再加密,比如Md5(username + password + saltString)。Spring 自带org.springframework.util.DigestUtils.md5DigestAsHex可以直接用,不用自己写 MessageDigest 那一堆代码。

注册的逻辑:

public boolean register(User user) { String username = user.getUsername(); User existUser = userMapper.selectByUsername(username); if (existUser != null) { return false; } String salt = "smart_mall_2024"; String md5Password = DigestUtils.md5DigestAsHex((username + user.getPassword() + salt).getBytes()); user.setPassword(md5Password); return userMapper.insert(user) > 0; }

登录成功后把 user 对象塞进 Session,这里只存必要字段,不要存密码。个人中心页面可以直接sessionScope.user.username展示用户名、昵称、头像,对应很多搜索词里说的“jsp 个人信息展示页面”。JSP 里访问用户头像要处理空值:

<c:if test="${not empty sessionScope.user.avatar}"> <img src="${sessionScope.user.avatar}" alt="头像" class="avatar" /> </c:if> <c:if test="${empty sessionScope.user.avatar}"> <img src="${pageContext.request.contextPath}/img/default.png" alt="头像" class="avatar" /> </c:if>

用户模块还有个很容易被忽略的点:修改个人信息时如果允许改手机号,需要先校验手机号是否已被其他账号绑定。如果不校验,会出现两个用户共用同一个手机号的情况,这在数据层没有唯一约束的情况下非常难排查。

4.2 商品列表分页、分类筛选与关键字搜索

商品展示是商城最核心的页面。前台首页展示轮播图,下面按分类展示商品,点击分类进入商品列表页。商品列表要处理三种情况:按分类查、按关键字搜索、不传任何条件查全部。如果页面只有一个搜索框,很容易把分类筛选丢了,所以在接收参数时要做优先级设计。

我一般用一个 ProductQuery 对象接收 pageNum、pageSize、categoryId、keyword 四个参数。Service 层先算 count 判断总页数,再查当前页数据,组装成 PageInfo 传给前端。JSP 里用 JSTL 的 c:forEach 遍历 productList,每个商品卡片循环输出图片、名称、价格,整页结构会非常清爽。

<c:forEach items="${pageInfo.list}" var="p"> <div class="product-card"> <a href="${pageContext.request.contextPath}/product/detail/${p.productId}"> <img src="${p.mainImage}" alt="${p.productName}" /> </a> <p class="title">${p.productName}</p> <p class="price">¥${p.price}</p> </div> </c:forEach>

搜索实现用 LIKE 匹配商品名称和副标题,注意 MyBatis 里写 LIKE 不要直接拼字符串,会有 SQL 注入风险。用concat('%', #{keyword}, '%')可以安全地拼接模糊查询,这也是黑马 JavaWeb 笔记里强调过的写法。分页条部分,如果数据量大可以自己写一个页码循环,也可以用现成的 PageHelper 前端工具,但毕设一般 30 个商品以内,首页展示 12 个、列表页每页展示 12 个就够,总页数不多,写起来也简单。

4.3 购物车模块与 Session/数据库的选择题

购物车数据存 Session 还是存数据库,这是毕设里非常关键的一个设计决策。存 Session 的开发量小,点击加入购物车后直接往 session 里的 list 塞数据,页面刷新数据还在,不用建表不用写 Join,只要一个 CartItem 实体就行。缺点是浏览器关了数据就没了、换设备不支持、而且购物车里的数量没法在后台查看。

我更推荐存数据库,哪怕多加两张表。理由很简单:订单结算时需要关联用户 ID 和商品 ID,购物车表天然是一张关联表,查数据、改数量、勾选结算都清晰。答辩时讲“购物车持久化到数据库,避免 Session 丢失”是一个非常体面的技术点。

加入购物车的核心逻辑是幂等合并,也就是说同一个用户加入同一个商品,如果购物车里已经有记录就 update 数量加一,没有记录才 insert 新行。这个判断必须先查一次:

Cart cart = cartMapper.selectByUserIdAndProductId(userId, productId); if (cart != null) { cart.setQuantity(cart.getQuantity() + 1); cartMapper.updateQuantity(cart); } else { Cart newCart = new Cart(userId, productId, 1, 1); cartMapper.insert(newCart); }

购物车页面修改数量、删除商品、勾选商品全部走单独接口,前端的 +/- 按钮用 Ajax 异步调后端,不整页刷新。勾选结算时前端把选中的 cartId 拼成逗号分隔字符串传给后端,后端用findByIds批量查出来,然后进入订单确认页。

4.4 下单流程与事务控制的完整性

下单选品是最需要表现“事务”能力的模块。流程是:接收一组购物车 ID -> 查出购物车明细 -> 校验库存是否足够 -> 扣减商品库存和增加销量 -> 生成订单主表记录 -> 生成订单明细 -> 删除已结算购物车记录。任何一个环节失败都不应该留下半单数据,所以 Service 层要加 @Transactional。

@Transactional public Order createOrder(List<Integer> cartIds, Long userId, String receiverName, String receiverPhone, String receiverAddress) { List<Cart> cartList = cartMapper.selectByIds(cartIds); BigDecimal totalAmount = BigDecimal.ZERO; ... // 校验库存 扣库存 for (Cart cart : cartList) { Product product = productMapper.selectById(cart.getProductId()); int rows = productMapper.deductStock(cart.getProductId(), cart.getQuantity()); if (rows == 0) { throw new RuntimeException("商品库存不足"); } } // 生成订单 Order order = new Order(); ... }

扣库存的语句用带条件更新防止并发超卖,这个点前面提过,一共三行 SQL,但写出来和没写出来在答辩时完全是两个评价。生成订单编号用System.currentTimeMillis()加三位随机整数拼接,再把订单明细循环插入。注意订单明细里要存商品名称、图片、当前价格,这些历史快照性数据千万不能偷懒,因为商品价格后续随时会变。

支付模块这里做一个模拟支付就够:订单状态初始为 0(待支付),点“去支付”之后把状态改为 1(已支付),记录支付时间。不用真的对接支付宝微信,否则还要申请商户号,属于给自己加戏。

5. JSP 前端页面与交互细节

5.1 用 JSP 做整套前端,页面结构怎么组织才不丑

很多人觉得 JSP 做前端很丑,其实丑的不是 JSP,是没做好公共部分抽取。最基础的做法是把头部、尾部、导航栏分别拆成 header.jsp、footer.jsp,页面用<%@ include %>静态包含引入。这样每个页面只写中间主体部分,代码量大幅下降,改导航栏也不用挨个页面改。

页面布局方面,我建议直接引入 Bootstrap 4 或者 Layui,它们自带栅格、按钮、表单样式,几分钟就能搭出一个像样的商城页面。Bootstrap 的 CDN 资源挂网上,注意要在<head>引入,JSP 的 EL 表达式在 HTML 标签属性里也都渲染,不会冲突。

首页布局一般是:顶部导航(登录状态显示用户名,未登录显示登录/注册)-> 轮播图 -> 推荐商品区。轮播图可以用 Bootstrap 的 carousel 组件,后台管理端可以删除或者改轮播图,前台只需根据公告表循环输出。这块代码不多,但涉及图片路径问题,建议所有图片统一存到项目 webapp/upload 目录下,页面拼接${pageContext.request.contextPath}/upload/+ 文件名。

5.2 图片坐标定位:用 HTML map + area 实现热区映射

搜索词里有人问“jsp 图片如何对坐标定位”,这其实就是 HTML 的 image map 技术。你在某个促销图上想把不同区域链接到不同商品分类,不需要切片重组图片,直接用<map>配合<area>定义热区就行。坐标定位基于图片左上角为原点的像素坐标。

我举一个实际场景:首页有一张电脑主板或商品分区宣传图,左边区域点击跳转到手机分类,右边区域跳转到笔记本分类。写法如下:

<img src="${pageContext.request.contextPath}/img/promotion.jpg" usemap="#promoMap" alt="活动图" /> <map name="promoMap"> <area shape="rect" coords="0,0,300,200" href="${pageContext.request.contextPath}/product/list?categoryId=1" alt="手机专区" /> <area shape="rect" coords="300,0,600,200" href="${pageContext.request.contextPath}/product/list?categoryId=2" alt="笔记本专区" /> </map>

coords 四个数字依次是 左上角 x、左上角 y、右下角 x、右下角 y。获取坐标可以在浏览器里按 F12 打开开发者工具,鼠标悬停图片查看位置信息,精确到像素。shape 除了 rect,还可以用 circle 画圆形热区,用 poly 画多边形热区,不过毕设里用矩形最省事。还有一点要提醒:图片尺寸如果被 CSS 改了,坐标就会偏移,所以热区图尽量不加 CSS 缩放,用原尺寸展示。

5.3 Ajax 异步交互与统一 JSON 格式

商城页面里,加入购物车、改数量、搜商品、注册时校验用户名都以 Ajax 为主。JSP 页面本身是动态渲染,但局部交互不整页刷新才现代。这里关键是后端接口返回数据结构要统一,我习惯统一一个 JSON 返回格式:

public class Result { private Integer code; // 0成功 1失败 private String msg; private Object data; }

注册页校验用户名的前端写法:

$.post("${pageContext.request.contextPath}/user/checkUsername", {username: username}, function(res) { if (res.code === 0) { $("#usernameMsg").text("用户名可用").css("color", "green"); } else { $("#usernameMsg").text(res.msg).css("color", "red"); } });

这里注意每次请求都带上 contextPath,不然部署到带项目名的路径下所有 Ajax 接口都会 404。也可以设置全局变量:

var ctx = "${pageContext.request.contextPath}";

然后把所有请求前缀换成ctx。这是我从多个项目里总结出来的最快解决方案。购物车改数量同理,点击加号时把 productId 和新的数量传给后端,后端更新数据库后返回最新的购物车总件数和总金额,前端局部刷新这两个数字。

5.4 拦截器实现登录校验,避免每个页面重复判断

登录态如果不做统一拦截,就需要在商品详情、购物车、订单每个 Controller 里手动判断 session,代码大量重复。SpringMVC 的 HandlerInterceptor 专门解决这个问题。新建 LoginInterceptor 类,preHandle 方法里从 session 取用户,没有就重定向到登录页:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); User user = (User) session.getAttribute("loginUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/user/login"); return false; } return true; } }

在 spring-mvc.xml 里注册拦截器并设置拦截路径:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/cart/**" /> <mvc:mapping path="/order/**" /> <mvc:mapping path="/user/info/**" /> <mvc:interceptor ref="loginInterceptor" /> </mvc:interceptor> </mvc:interceptors>

拦截器配置好后,后台管理端再单独配一个 AdminInterceptor,拦截 /admin/** 路径,检查 session 里是否是管理员账号。这两个拦截器能让权限控制非常清晰,也让你在答辩时讲解“系统如何保证未登录用户不能下单”时顺理成章。

6. 高频报错与实战排查

6.1 IDEA 运行 JavaWeb 项目最容易遇到的三个问题

每年带毕设都会看到同样的报错,这里集中整理一下。第一个是页面 404。原因通常是 IDEA 里 Tomcat 的 Deployment 没把项目加进去,或者 Application context 配错了。解决方式是在 Run Configuration 的 Deployment 页签添加 Artifact,注意选 war exploded,不要选 war,不然每次改动页面都要重新打包。访问路径直接用配置里的 Application context,比如 /smart。

第二个是依赖 jar 包没打包。报错通常是java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet。原因是 IDEA 的 Artifact 里没有把 Maven 依赖带入 WEB-INF/lib。检查 Project Structure -> Artifacts -> 选中 Output Layout,点 Available Elements,把项目依赖全部右键 Put into /WEB-INF/lib。这个问题几乎每个人都会踩,知道原理后两分钟搞定。

第三个是 Tomcat 端口被占用。启动时报Port 8080 was already in use。方案有两个,一个是改 Tomcat 配置的端口,从 8080 改成 8081;一个是找到占用进程杀掉。我建议直接改端口,因为杀掉进程可能误杀别人项目。IDEA 里修改 Tomcat Server 的 HTTP port 即可,不用改 server.xml,IDEA 会自动覆盖。

6.2 中文乱码的正确处理顺序

中文乱码在 JSP + JavaWeb 项目里是必考问题,处理顺序错了就会一直乱。第一层是数据库连接 URL 加参数characterEncoding=utf8,这保证写入和读取数据库时按 UTF-8 传输。第二层是 web.xml 里配置 CharacterEncodingFilter,过滤编码设为 UTF-8,这保证请求参数和响应输出按 UTF-8 处理。第三层是每张 JSP 页面顶部加<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>,保证 JSP 编译时用 UTF-8。

Tomcat 8 及以上 GET 请求默认 URIEncoding 已经是 UTF-8,旧版 Tomcat 需要在 server.xml 的 Connector 加URIEncoding="UTF-8"。如果三层都配了还是乱码,八成是数据库表本身的字符集不是 utf8mb4,检查建表语句或者用下面这句修复:

ALTER TABLE t_user CONVERT TO CHARACTER SET utf8mb4;

这个排查顺序我反反复复用,基本覆盖 99% 的乱码场景。

6.3 高频报错速查表

  • 启动报 BeanCreationException:spring-mybatis.xml 里 SqlSessionFactoryBean 的 mapperLocations 路径写错,比如 classpath:mapper/*.xml 的位置和实际不一致,或者 Druid 数据源账号密码错误
  • 只显示标签不显示数据:JSP 用的是 ${} 但在 web.xml 里没有引入 jstl 依赖,或者 Web 项目没有把 jstl jar 包一起打包到 WEB-INF/lib
  • 数据库连接报 Public Key Retrieval is not allowed:JDBC URL 后面加allowPublicKeyRetrieval=true,这是 MySQL 8 和 Connector/J 8 的认证变化导致的
  • MyBatis 报 Invalid bound statement (not found):Mapper 接口和 XML 文件的 namespace 不匹配,或者 Mapper 接口方法名跟 XML 中 id 不一致
  • 图片上传失败:目录不存在。上传代码里要先创建 upload 目录,File dir = new File(request.getServletContext().getRealPath("/upload")); if (!dir.exists()) dir.mkdirs();

这些报错的信息基本上看一眼报错位置就能定位。最忌讳的是乱试乱改,排查要按层次来,先看配置文件,再看依赖,再看路径,效率最高。

7. 数据库初始化与项目部署上线

7.1 用现成数据脚本快速填充,商品别凑数

数据库建的再完整,没有数据前端页面也是空荡荡的。这里非常推荐网上 JavaWeb 笔记里整理好的商城数据库脚本,很多培训机构资料里都有现成的商品数据、分类数据、用户数据,直接导入数据库比自己手动 INSET 快得多。

但要注意,直接拿来用的脚本往往存在几个问题:一是商品图片地址是绝对路径或者死链,页面图片全部裂掉;二是用户密码可能是明文或者旧加密方式,跟你代码里的 MD5 加盐逻辑对不上;三是分类表和商品表的字段名可能跟你的实体类不一致。所以我的建议是参考脚本里的数据,把商品名称、价格、分类这些核心信息导入到自己的表里,图片地址重新改成你的项目路径,密码重新生成一版符合你加密逻辑的。

商品图片建议先用本地图片,放到 webapp/upload,然后 SQL 里 UPDATE 图片路径。等页面能正常显示,再决定要不要换线上图床。这一步做好了,前台页面立刻从裸奔状态变成看起来像模像样。

7.2 Maven 打包 war 和传统 Tomcat 部署

开发期在 IDEA 里跑 Tomcat 很爽,但正式要演示、要部署到实验室服务器,就得打一个 war 包。在 IDEA 最右侧 Maven 面板执行clean package,命令执行完整后 target 目录下会有smart-mall.war。把这个 war 拷贝到 Tomcat 安装目录下的 webapps 文件夹,启动 Tomcat(bin/startup.bat 或 bin/startup.sh),Tomcat 会自动解压 war 包,然后就能通过http://ip:8080/smart-mall/访问项目了。

这里有一个必须注意的点:本地 MySQL 数据库的账号密码是 root/123456,换到服务器上要改成服务器的数据库配置,并且数据库需要先导入建表和初始化脚本。如果你用本地开发数据库,演示的时候换一台机器就连不上库,页面直接报错。所以部署前检查点:数据库能连通、账号有权限、编码是 UTF-8、图片路径能找到、war 包是最新版本。

后台管理入口建议单独设置,比如/admin/login,客户端不展示这个入口,避免演示时误入。管理员表可以固定插入一条 admin 账号,密码同样 MD5 加密,答辩前务必记得这个账号密码。

8. 一些实操后的总结体会

最后再写点个人的感受。去年有学弟拿着 SSM 智慧商城平台来找我,说写了一半卡在购物车勾选结算上。我看了他的代码,是前端把 table 里所有 checkbox 的 id 拼成了字符串,后端用逗号 split 后转 List ,查询时才发现购物车表根本没有 checked 字段,只能先查全部再在代码里过滤。后来加了字段,把查询改成IN语句,两小时就通了。我举这个例子的意思是,很多你觉得复杂的地方,其实都是因为表设计少考虑了一个字段,或者没有按业务域拆表。基础打好了,编码环节反而是最快速的。

如果你时间紧,我的建议是先把数据库设计做好,再把用户、商品、购物车、订单这条主干跑通,最后补后台管理和页面美化。顺序一定不要反,不要一上来就研究轮播图怎么轮播,那不是核心。核心是数据流转正确、事务完整、状态可行。这几点做到位,答辩时基本稳稳的。

另外一个小的实操技巧:代码里全局搜索 TODO、System.out.println,把所有调试输出清理掉或改成 logger。不要小看这个细节,演示环境控制台疯狂刷 println,老师一眼就能看出代码不整洁。提交前再跑一遍全流程:注册新用户、登录、加购、下单、后台发货,模拟真实用户完整走一遍,确保没有断链和空指针。做到这一步,你离一个优质毕设就差表设计文档和演示视频了。项目踩坑记录和文档放一起建个 docs 文件夹,答辩前翻一遍,你比坐在台下的大多数同学都稳。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 3:00:45

ACPI设备扩展与ISA总线:从49个扩展对象看内核调试方法

前阵子调试一台旧测试机&#xff0c;ACPI驱动在枚举ISA空间设备时反复走ACPIBuildDeviceExtension这个例程&#xff0c;我顺手在断点上把扩展对象的数量数了一遍&#xff0c;最后得到1236149个。这个数字本身没什么魔法&#xff0c;但它背后藏着ACPI驱动对ISA总线的处理方式、设…

作者头像 李华
网站建设 2026/10/7 2:59:50

AI智能体技能包Skills实战指南:从原理、部署到API集成

这次我们不聊某个具体模型&#xff0c;先来看一个在 AI 智能体圈子里越来越常见的概念&#xff1a;Skills。你可以把它理解为给 Agent 预装的“技能包”。它不需要重新训练模型&#xff0c;也不用改底层权重&#xff0c;而是通过一份结构化的指令文件&#xff0c;让智能体在遇到…

作者头像 李华
网站建设 2026/10/7 2:59:32

Claude Code实战:从椋鸟群飞到软体物理的AI编程代理指南

如果你最近刷到过“Claude 纯代码演算 16 万只椋鸟”“四大模型魔方绝杀对抗”“果冻软体物理实测”这类视频或直播切片&#xff0c;大概率会有两个反应&#xff1a;先被画面震撼&#xff0c;然后产生一个更实际的疑问——这到底只是节目效果&#xff0c;还是 AI 编程真的能完成…

作者头像 李华
网站建设 2026/10/7 2:59:32

从文本型到扫描型:福昕PDF编辑器与OCR识别实战指南

在使用 PDF 编辑器这件事上&#xff0c;很多人的感受是&#xff1a;平时用不到的时候觉得无所谓&#xff0c;一旦需要修改合同、填写扫描件、提取表格文字&#xff0c;才意识到手里没有一个趁手的工具有多麻烦。网上能免费转格式的网页工具倒是不少&#xff0c;但要么限制页数&…

作者头像 李华
网站建设 2026/10/7 2:59:03

基于SpringBoot+Vue的树洞论坛系统:从表结构到前后端部署

简介&#xff1a;这是一份基于SpringBoot与Vue的树洞论坛系统完整源码&#xff0c;目标读者是计算机相关专业毕业生、全栈开发初学者&#xff0c;以及需要快速搭建可演示项目的人群。项目围绕匿名倾诉与问答交流场景&#xff0c;实现了用户管理、问题发布、回答互动、敏感词过滤…

作者头像 李华
网站建设 2026/10/7 2:58:58

BQ25798光伏MPPT升降压充电芯片深度解析

1. 项目概述&#xff1a;一块芯片如何让光伏充电系统真正“聪明”起来你有没有遇到过这样的场景&#xff1a;屋顶上铺着崭新的光伏板&#xff0c;阳光正烈&#xff0c;可接上铅酸或锂电储能系统后&#xff0c;电池充得慢、发热大&#xff0c;阴天时甚至根本充不进去&#xff1f…

作者头像 李华