☰
SSM家乡特产商城毕业设计源码解析:架构部署与避坑指南
2026/10/27 23:14:58 网站建设 项目流程

简介:面向Java毕业设计与课程设计的综合性实战资源,基于SSM框架与Vue.js构建家乡特产网上商城,采用B/S架构搭配MySQL数据库,覆盖前端页面、后端业务逻辑与数据库表结构,适合需要完整项目源码、演示与说明材料的高校学生参考。资源共803个文件,包含114个Java源码、60个Vue组件、159个JavaScript脚本、38个HTML页面及51个样式表,另附SQL数据库脚本、演示视频、项目说明及运行辅助脚本;整套压缩包约42.72MB,目录结构清晰,便于按模块研读。已有125人浏览学习,适合作为毕业设计选题时的直接参照。可获取完整前端商城页面与后端接口实现、管理员与用户双视角操作演示录屏、数据库初始化脚本及搭建说明等,帮助快速理解SSM+Vue在真实商城场景中的整合方式,有效缩短设计与开发周期。

1. 从课程设计到答辩演示:这个 SSM 家乡特产商城能直接改着用

如果你正在找一份能跑通、能讲清楚、能自己改的 Java 毕业设计,那基于 SSM 框架的家乡特产网上商城源码包可能是你今晚就能用上的东西。它用 Spring + SpringMVC + MyBatis 搭了一个完整的前后台商城,前台有商品浏览、购物车、下单,后台有商品管理、订单管理、用户管理,还带演示视频和一份能照着做的说明文档。对课程设计、毕业设计来说,它解决的不只是“有代码”,而是让你从启动项目到答辩讲原理都有一条清晰的路。适合已经会 JDK、MySQL、Maven 基础操作,但没时间从零搭商城的人。下面我会把它的结构和启动细节拆给你看。

2. 系统拆解:权限设计、数据库表与 SSM 目录结构

2.1 三类角色与功能模块边界

这个商城的业务边界很清晰,按角色分成游客、注册用户、管理员三套操作逻辑。

  • 游客:看商品分类、商品列表、商品详情,不需要登录。
  • 注册用户:登录后能加入购物车、提交订单、查看自己的订单、维护个人资料。
  • 管理员:登录后台后管理分类、商品上下架、订单发货和用户状态。

从源码的 Controller 命名就能看出这种边界。ProductController 管前台商品,CartController 管购物车,OrderController 管订单,AdminProductController 和 AdminOrderController 管后台。每个 Controller 只处理自己职责内的请求,答辩时你直接说“订单相关的请求都进 OrderController”,评委就能理解你的分层思路。我见过不少课程设计把所有接口堆在一个 Controller 里,虽然也能跑,但写 service 层时根本没法维护。

权限控制不是靠 AOP 或 Shiro 那种重框架,而是靠 SpringMVC 拦截器加 Session 完成的。登录成功后把用户对象放进 Session,后台路径再单独配一个管理员校验。这样最简单,也最容易跟评委解释。后面第 4 章我会把拦截器代码贴出来。

2.2 表结构设计:订单状态机与价格快照

数据库是这个项目里最值得讲的部分。核心表有五张:用户表、分类表、商品表、订单表、订单项表。先看用户表。

CREATE TABLE `tb_user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '登录名', `password` VARCHAR(100) NOT NULL COMMENT 'MD5 加密后密码', `nickname` VARCHAR(50) DEFAULT NULL, `phone` VARCHAR(20) DEFAULT NULL, `role` TINYINT DEFAULT 0 COMMENT '0-普通用户 1-管理员', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里用 role 字段区分用户和管理员,而不是单独建管理员表。对课程设计来说,一张用户表加一个角色字段足够,还能少维护一套登录逻辑。密码不要明文存,资源里用了 MD5 加密,虽然强度一般,但比明文好太多。如果你想让答辩更有亮点,可以在原有基础上改成 MD5 加盐,改动很小。

商品表要特别注意价格和库存字段。

CREATE TABLE `tb_product` ( `id` INT NOT NULL AUTO_INCREMENT, `category_id` INT NOT NULL, `name` VARCHAR(100) NOT NULL, `origin` VARCHAR(100) DEFAULT '' COMMENT '产地,比如福建宁德', `price` DECIMAL(10,2) NOT NULL, `stock` INT NOT NULL DEFAULT 0, `image_url` VARCHAR(255) DEFAULT '', `description` TEXT, `status` TINYINT DEFAULT 1 COMMENT '1-上架 0-下架', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

价格字段用 DECIMAL(10,2) 而不是 DOUBLE,这是为了避免浮点误差。商品不上架不是删除记录,而是把 status 改成 0,这样历史订单里还能关联到商品信息。origin 字段是“家乡特产”这个主题留下来的业务痕迹,你可以把它改成自己的家乡地名,整个系统就变成你的作品了。

订单表是状态流转的核心。

CREATE TABLE `tb_order` ( `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `order_no` VARCHAR(32) NOT NULL COMMENT '业务单号', `total_amount` DECIMAL(10,2) NOT NULL, `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0-待付款 1-已付款待发货 2-已发货 3-已签收 4-已取消', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

订单状态定义成一个 0~4 的整数,比字符串好存储好比较。我给你一个状态机的判断逻辑:待付款可以转已付款待发货,也可以转已取消;已付款待发货只能转已发货;已发货只能转已签收。这个流转规则在代码里用常量类或者枚举体现,而不是散落的 if 判断。

订单项表是整个库设计里最容易加分的地方。

CREATE TABLE `tb_order_item` ( `id` INT NOT NULL AUTO_INCREMENT, `order_id` INT NOT NULL, `product_id` INT NOT NULL, `product_name` VARCHAR(100) NOT NULL COMMENT '冗余商品名', `price` DECIMAL(10,2) NOT NULL COMMENT '快照价格', `quantity` INT NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这张表把商品名称和价格直接复制了一份,而不是每次下单都去 join 商品表。为什么?因为用户下单后商品可能改价甚至下架,订单明细必须保留用户下单那一刻的商品名和价格。这叫“数据快照”,在企业订单系统里是标配。答辩时能说出这一点,评委对你的印象会不一样。

2.3 目录结构:为什么源码看起来乱但其实是标准三层

拿到源码后的第一件事,先看 src 目录长什么样。

src/main/java/com/shop/ ├── controller/ # 控制器层 ├── service/ # 业务接口 ├── service/impl/ # 业务实现 ├── mapper/ # MyBatis Mapper 接口 ├── pojo/ # 实体类与 VO/DTO └── config/ # Spring 配置类、拦截器配置

controller 层只接收参数和返回视图,service 层写业务规则,mapper 层做数据库交互。很多同学拿到别人源码后喜欢直接改 controller,把 SQL 写在 controller 里,这是毕设翻车最严重的问题——一旦功能逻辑复杂,代码根本没法调试。你要坚持让 service 层承担主要业务,controller 越薄越好。

资源里还有 resources 目录,下面放着 jdbc.properties、spring-mvc.xml、spring-mybatis.xml、mybatis-config.xml 等配置文件。有人会觉得 XML 配置繁琐,但 SSM 课程设计就是要你用 XML 把“容器管理哪些包、拦截哪些路径”讲清楚,这就是教学目的。

2.4 选型理由:SSM 在毕设场景下的不可替代性

现在很多新项目都转向 Spring Boot,但高校课程设计和毕业设计里 SSM 依然占很大比例。原因是 SSM 的 XML 配置是显式写出来的:配置类扫描包、配置拦截器、配置数据源,每一步都见得到。Spring Boot 则是自动配置,很多细节是黑匣子,你很难跟评委解释“为什么写一个 starter 就能跑”。所以这份资源选型 SSM 不是落后,而是教学场景下的匹配选择。

如果你时间富余,可以在跑通后把配置迁移到 Spring Boot,但我不建议为了“新”硬改。课程设计答辩重点是你对 MVC 流程、MyBatis 映射、事务控制的理解,SSM 反而更容易展示这些基础原理。把这个项目跑透,前后台流程走一遍,那些框架之间的协作关系你就清楚了。

3. 环境搭建:JDK、MySQL、Maven 到 Tomcat 的启动全流程

3.1 版本组合与核心配置文件

SSM 项目最大的坑是版本不匹配。资源说明文档里如果写了版本,严格按说明来。如果没有写,我建议用下面这套兼容性最好的组合:

组件常见可选版本说明
JDK1.8SSM 稳定运行的基础版本
MySQL5.7 / 8.05.7 兼容性最好,8.0 需要换驱动
Maven3.6.x依赖管理
Tomcat8.5支持 Servlet 3.1
Spring5.x与 JDK 1.8 搭配

连接数据库的配置在 jdbc.properties 里,名字可能略有不同,但核心字段是一致的:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

参数说明:useUnicode 和 characterEncoding 保证中文不乱码,serverTimezone 是 MySQL 8 驱动强制要求的时区参数。如果你使用 MySQL 5.7,但是驱动版本也很老,这个参数反而可能报错。所以拿到源码后先确认本地 MySQL 版本,再决定 url 里留不留 serverTimezone。这个问题每年都有人踩。

Spring 的数据源配置通常在 spring-mybatis.xml 或 applicationContext.xml 里:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>

DriverManagerDataSource 是 Spring 自带的简易数据源,适合课程设计,性能和连接池都比不上 Druid。如果你想让项目更像企业级,可以把 class 换成 com.alibaba.druid.pool.DruidDataSource,再引入 Druid 依赖,代码层面改动只有这一个 bean。我一般会保留简易版,因为评委看的是 MyBatis 和 Spring 的整合逻辑,不在数据源上纠结。

3.2 数据库初始化与 Maven 打包

第一次启动前,数据库需要手动创建。说明文档里一般会提到一个 sql 文件,路径通常在 resources/sql/ 下。

mysql -uroot -p -e "CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4;" mysql -uroot -p shop < src/main/resources/sql/shop.sql

这里有几个参数要理解:第一个命令创建数据库,指定 utf8mb4 字符集;第二个命令把 sql 文件导入 shop 库。注意导入时终端要能识别 < 符号,Windows 的 cmd 也可以直接用这个语法。如果你用的是 IDEA 的数据库工具,也可以打开 sql 文件直接执行。

接下来是 Maven 打包。源码通常是一个完整工程,在项目根目录执行:

mvn clean package -DskipTests

skipTests 表示跳过测试用例,毕设项目一般没有单元测试,但这个参数能避免因为测试类报错导致打包失败。打包完成后 target 目录下会生成 war 包,把这个 war 丢到 Tomcat 的 webapps 目录,再启动 Tomcat 就完成部署了。如果你用的是 IDEA,可以直接配置 Tomcat Server,把部署工件选成 war exploded,这能省去每次改代码重新打包的时间。

3.3 IDEA 部署与启动排查

IDEA 里部署 SSM 项目有一个单独的 Artifacts 配置过程。打开 Project Structure,添加一个 Web Application Exploded,把依赖的 lib 加进去,然后配置 Tomcat 的 Deployment。如果你第一次接触这个流程,建议直接看演示视频里 IDEA 部署那一段,比文字描述直观得多。视频里通常会演示从导入项目到浏览器打开首页的全过程,照着做一次就能跑起来。

启动 Tomcat 后访问路径要注意 context path。如果 war 包名称是 shop.war,访问地址一般是:

http://localhost:8080/shop/

如果你看到 404,先不要怀疑代码,去 Tomcat 的 webapps 目录确认 war 是否解压成 shop 文件夹。Tomcat 解压失败的原因很多,比如 web.xml 里 spring 配置文件路径写错,或者 jar 包冲突。此时打开 Tomcat 日志,也就是 catalina.out 或 console 标签页,看有没有 Exception。

3.4 项目里前端资源与 Vue 引入方式

资源里前端页面不是纯 JSP,而是引入了 Vue.js。常见做法是在 JSP 页面底部用 CDN 引入 Vue 文件:

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script src="${pageContext.request.contextPath}/js/product.js"></script>

如果你的电脑没有外网,CDN 文件加载不出来,页面就完全没有交互效果。最简单的解决办法是把 vue.js 文件下载下来放到 webapp/js 目录,改成本地路径。很多同学卡在这里,页面能打开但列表不渲染,第一反应是 JS 代码错了,其实只是 CDN 没加载成功。你把 url 复制到浏览器单独访问一次,如果打不开那就是网络问题。

4. 核心代码:登录鉴权、购物车与订单状态机的实现

4.1 登录鉴权:拦截器与 Session 线程绑定

登录功能在 SSM 项目里的标准实现是 HandlerInterceptor。用户登录成功后,把用户信息存进 Session,拦截器在请求到达 Controller 之前检查 Session。这里是一个最简洁的拦截器写法:

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) { response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }

逻辑说明:preHandle 在 Controller 方法执行前运行。user 为 null 说明没有登录,sendRedirect 跳到登录页,return false 终止后续执行。user 存在就 return true 放行。

在 SpringMVC 配置文件里注册这个拦截器,并指定放行路径:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/register"/> <mvc:exclude-mapping path="/product/**"/> <mvc:exclude-mapping path="/static/**"/> <bean class="com.shop.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>

参数说明:mapping path="/**" 表示拦截所有路径;exclude-mapping 是白名单;product 下商品列表和详情必须放行,因为游客也要看商品;静态资源放行是避免 CSS、JS 被拦截后页面样式丢失。你拿到的源码里可能还多一个 admin 的 exclude 配置,那是给后台登录页用的。

这个方案在答辩时很好讲,但要留意一个体验问题:如果你用 Ajax 请求,登录过期后后端返回的不是 JSON,而是重定向指令,前端 Vue 会拿不到数据。如果你想做得更完善,可以判断请求头里是否带 X-Requested-With,但那是进阶内容,能说出来也是加分项。

4.2 购物车:Cookie 方案与数据库方案的选择

购物车这部分的实现,不同源码包差别很大。我见过这个类型资源里有几种思路,有的是登录后才能加购物车,用表存储;有的是用 Cookie 存 JSON,游客也能加。两种各有道理,如果你拿到的是 Cookie 版本,核心逻辑大概是这样的:

private void addToCart(HttpServletRequest request, HttpServletResponse response, Integer productId, Integer quantity) { Cookie[] cookies = request.getCookies(); List<CartItem> cart = new ArrayList<>(); if (cookies != null) { for (Cookie cookie : cookies) { if ("cart".equals(cookie.getName())) { cart = JSON.parseArray(cookie.getValue(), CartItem.class); break; } } } boolean exists = false; for (CartItem item : cart) { if (item.getProductId().equals(productId)) { item.setQuantity(item.getQuantity() + quantity); exists = true; break; } } if (!exists) { cart.add(new CartItem(productId, quantity)); } Cookie cookie = new Cookie("cart", JSON.toJSONString(cart)); cookie.setMaxAge(7 * 24 * 3600); cookie.setPath("/"); response.addCookie(cookie); }

逻辑说明:先从 request 里取出名为 cart 的 Cookie,解析成购物车列表;然后遍历列表,如果商品已存在就累加数量,不存在就新增;最后把列表写回 Cookie,设置七天有效期。Cookie 的 path 要设置成 /,否则只在当前路径下有效,换一个页面就读不到。

Cookie 方案的好处是未登录也能加购物车,后端少维护一张表;坏处是换设备购物车不同步。如果你希望答辩更有商业系统逻辑,可以在登录后把购物车同步到数据库。具体做法是建一张 tb_cart 表,字段为 user_id、product_id、quantity,加唯一索引 user_id + product_id。登录成功后,从 Cookie 读一次并批量写入数据库,然后清掉 Cookie。这个改进能讲十分钟。

4.3 订单状态机与事务边界

订单创建是 SSM 项目里最需要谨慎的地方,因为它涉及多张表的数据一致性。一次下单操作要写订单表和订单项表,还要扣库存。这里必须用事务。

@Service public class OrderServiceImpl implements OrderService { @Transactional @Override public Order createOrder(Integer userId, List<CartItem> items) { // 1. 生成订单主表,status 置为 0 待付款 // 2. 遍历 items 写入 order_item 表 // 3. 扣减商品库存,库存不足则抛异常触发回滚 // 4. 清空用户购物车 return order; } }

逻辑说明:@Transactional 声明事务边界,方法内任何 RuntimeException 抛出都会让前面所有数据库操作回滚。库存扣减这一步,在 SQL 里就应该带上 stock > 0 的条件,否则并发场景会出现超卖。

UPDATE tb_product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity}

这里影响行数为 0 表示库存不足,Java 层拿到这个结果后抛异常。为什么要用一行 SQL 而不是先查库存再扣减?因为“先查再改”在并发下会出现两个请求同时读到库存 1,然后都执行扣减,最后库存变成 -1。用条件更新可以在数据库层面挡住超卖。你能在答辩时说出这条,订单设计的分数就跑不掉了。

订单状态流转建议用一个状态判断方法:

public boolean canTransit(Integer from, Integer to) { return switch (from) { case 0 -> to == 1 || to == 4; case 1 -> to == 2; case 2 -> to == 3; default -> false; }; }

这个 switch 表达式是 Java 14 的语法,源码如果是 JDK 1.8 就换成 if-else。它的作用是把状态流转规则集中在一个方法里,避免在 Controller 里到处写 if (status == 0 && action == 1)。状态流转集中管理后,后台订单管理的每个按钮只需调用这个方法判断是否能操作。

4.4 MyBatis 分页与商品列表查询

商品列表必然涉及分页,SSM 里最常见的是 PageHelper 插件。在 Maven 依赖里加入:

<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.0</version> </dependency>

然后在 spring-mybatis.xml 里配置插件:

<property name="plugins"> <array> <bean class="com.github.pagehelper.PageInterceptor"> <property name="properties"> <value>helperDialect=mysql</value> </property> </bean> </array> </property>

参数说明:helperDialect 告诉插件目标数据库类型是 MySQL,PageHelper 会拦截接下来执行的查询 SQL 并自动拼接 limit 语句。Mapper 接口方法不需要传 limit 参数,而是在 Service 层调用 PageHelper.startPage(pageNum, pageSize) 后紧跟着查询方法。

public List<Product> listByCategory(Integer categoryId, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); return productMapper.selectByCategory(categoryId); }

这里有个使用规范:PageHelper.startPage 后面必须紧跟第一条查询语句,中间不能插入其他 SQL,否则分页会作用到错误的查询上。这是一个高频翻车点,我在第 5 章会专门提。

4.5 Vue 局部渲染与后端接口返回

资源里的前台页面用 Vue 做列表和购物车小红点刷新。前端页面初始化时通过 axios 请求后端接口,拿到 JSON 数组渲染到页面。

new Vue({ el: '#app', data: { productList: [] }, created() { this.loadProducts(); }, methods: { loadProducts() { axios.get('/shop/product/list') .then(res => { this.productList = res.data; }); } } });

对应的后端 Controller 要返回对象而不是页面,需要加 @ResponseBody:

@RequestMapping("/list") @ResponseBody public List<Product> list(@RequestParam Integer categoryId) { return productService.listByCategory(categoryId, 1, 10); }

注意这里的路径axios.get('/shop/product/list'),开头是 /shop,这是 context path。如果你部署时改了 war 包名称,这段 axios 里的前缀也要同步改,否则请求会 404。很多人把精力放在后端调试,忽略了前端 JS 里的路径和部署环境耦合,这是前端集成 SSM 最常见的坑。

在 SpringMVC 配置里还要记得加注解驱动:

<mvc:annotation-driven/>

没有这个标签,@ResponseBody 不会生效,接口会直接返回 ModelAndView 的视图名,前端拿到的是字符串而不是 JSON。这个问题属于“配置缺失到怀疑人生”的类型,且这类配置通常在 xml 里,不看日志很难发现。

5. 避坑排查:SSM 毕业设计最常见的 5 个翻车点

5.1 中文乱码:JSP 页面和 MySQL 双端不一致

现象:商品名称在页面显示成问号,或者插入数据库后变成??。 原因:Tomcat 接收请求时默认字符集不是 UTF-8,MySQL 连接串没带 characterEncoding,或者建表时用了默认 latin1。 解决:统一字符集。jdbc.properties 里加 useUnicode=true&characterEncoding=utf8;JSP 页面头部加 pageEncoding="UTF-8";建库时指定 utf8mb4。如果库已经建好,执行 ALTER DATABASE shop CHARACTER SET utf8mb4; 再把表也改掉:

ALTER TABLE tb_product CONVERT TO CHARACTER SET utf8mb4;

这个转换命令会重建表,表数据量小的时候没影响。改完后建议重启 Tomcat 再试,因为连接连接池里的旧连接可能还保持着老编码。

5.2 连接 MySQL 8 报错:驱动类找不到或时区异常

现象:启动报ClassNotFound: com.mysql.jdbc.Driver,或者The server time zone value is unrecognized。 原因:MySQL 8.0 开始驱动包名改成com.mysql.cj.jdbc.Driver,而且连接必须指定 serverTimezone。 解决:在 pom.xml 或 lib 目录里确认驱动包是 mysql-connector-java 8.x,然后把配置改成:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

如果你的 MySQL 是 5.7,驱动是 5.1.x,就不要加 serverTimezone,否则老驱动会报参数不支持。判断依据是看项目里 lib 文件夹下驱动的 jar 版本,不看 MySQL 服务端版本。

5.3 页面样式全丢:拦截器把静态资源拦截了

现象:登录后页面能开,但 CSS、JS 和 Vue 文件全部 404,前端样式完全错乱。 原因:拦截器配置了/**拦截所有请求,但没有放行 static 目录,SpringMVC 也没配置静态资源映射。 解决:先加静态资源映射:

<mvc:resources location="/static/" mapping="/static/**"/>

然后在拦截器 exclude 里放行 static:

<mvc:exclude-mapping path="/static/**"/>

如果页面里的资源路径不是/static/开头,而是css/,那就把 location 和 mapping 改成对应路径。这个问题的排查方法很简单:看浏览器开发者工具里报 404 的资源路径,反推项目里实际存放位置,再改配置。

5.4 MyBatis 查询结果字段全为 null

现象:SQL 在数据库工具里执行有值,但页面显示商品价格是 0.0,名称是 null。 原因:数据库字段是下划线命名 user_id,Java 属性是驼峰 userId,MyBatis 默认不会自动映射。 解决:在 mybatis-config.xml 里开驼峰映射:

<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>

开了这个开关之后,绝大多数下划线字段能自动对应到驼峰属性。如果你用了SELECT *之外的写法,给字段起别名比如user_id AS uid,映射又会失效。因为别名直接覆盖了字段名,MyBatis 从结果集取列名时只能看到 uid,找不到对应的 userId 属性。所以别随手写别名。

5.5 PageHelper 分页错乱或丢失 order by

现象:第二页数据还是第一页的内容,或者分页时排序失效。 原因:PageHelper.startPage 后不是立即查询,中间执行了其他 SQL,导致分页插件作用到错误查询上。 解决:强制要求 startPage 和下一行必须紧跟 Mapper 查询:

PageHelper.startPage(pageNum, pageSize); return productMapper.selectByCategory(categoryId);

如果你在 startPage 和 select 之间写了一条日志 INSERT 操作,这条日志 SQL 会被分页,页面上数据就全乱了。我一般还会在 Controller 里把分页结果封装成 PageInfo,这样前端能拿到 total 这个关键字段:

PageInfo<Product> pageInfo = new PageInfo<>(productList);

PageInfo 里包含总页数和当前页,比直接返回 List 更适合前端渲染分页条。这个封装没有额外成本,答辩时还能顺带讲一讲插件原理。

6. 进阶验证:SSM 后端挂 Vue 前端,跨域配置与接口连通性检查

如果你想把这份源码改得更有企业味,最直接的一个方向是把前端页面和服务端拆成两个独立端口运行。SSM 后端仍然提供接口,Vue 页面单独部署在另一个端口,这样前后端通过 Ajax 通信,就会碰到跨域问题。SpringMVC 里可以用一个配置类解决:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:8081") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true); } }

参数说明:allowedOrigins 写前端页面的完整访问来源,/api/**表示只对接口路径开放跨域;allowCredentials(true) 允许携带 Cookie,这样 Session 登录状态在跨域下依然有效。如果不打开 credentials,axios 默认不会把 Cookie 带过去,你会发现跨域能访问但接口提示未登录。

配置好后,用 curl 验证接口连通性是最快的。先登录拿 Session Cookie,再访问商品列表:

curl -c cookie.txt -d "username=admin&password=123456" http://localhost:8080/shop/admin/login curl -b cookie.txt http://localhost:8080/shop/api/product/list

第一条命令把响应里的 Set-Cookie 保存到 cookie.txt,第二条命令带上这个 Cookie 请求接口,返回 JSON 就说明登录链路和接口都正常。没有 curl 的话,用浏览器开发者工具同样能看到请求头里的 Cookie 和响应 JSON。

这个验证方法是我拿到每一份 SSM 源码后必做的第一件事。跑通登录-访问主流程只需要五分钟,却能筛掉大量隐藏问题。从那以后我每次遇到新源码,都会强制自己先走一遍 curl 的登录和列表请求,确认后端链路通畅之后,才去调前端页面。这套检查思路对课程设计答辩前的排错尤其有用,希望帮到你。

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

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

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

立即咨询