ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSM超市管理系统课程设计:从搭建到避坑的完整实战指南

SSM超市管理系统课程设计:从搭建到避坑的完整实战指南

简介:这是一份基于SSM(Spring + Spring MVC + MyBatis)框架的超市管理系统课程设计资源,面向计算机相关专业学生及需要快速完成SSM整合项目的开发者。系统涵盖商品管理、库存管理、销售管理、会员管理及统计报表等核心模块,后端以Spring管理依赖与事务、Spring MVC处理请求、MyBatis操作数据库,前端包含JSP视图、HTML页面及配套CSS、JavaScript脚本。资源包共792个文件,压缩后约1.78MB,以452个HTML页面为主体,辅以48个Java源码、48个class编译文件、28个XML配置、41个JS脚本、12个JSP视图及SQL数据库脚本,结构完整便于直接导入运行。目前已有84人学习浏览。对于课程设计或SSM框架学习者而言,这套可运行的项目提供了清晰的分层代码结构、后台管理界面和数据库设计脚本,可直接参考复用,也可按需扩展新功能模块。

1. SSM超市管理系统课程设计:一份能跑通、能讲清、能改动的SSM架构参考

很多人在课程设计里拿到“超市管理系统”这个题目时,第一反应是去开源社区找个SSM项目直接改。但真等下载解压,问题就来了:配置文件互相报错、jar包版本对不上、前端页面和后端接口各说各话,运气差的连Tomcat启动都过不去。这套SSM超市管理系统,就是为这个场景准备的:它把Spring管理对象、Spring MVC分发请求、MyBatis处理数据库这三件事完整跑通,模块覆盖管理员登录、商品与供应商管理、进货与销售订单,代码按Controller-Service-Dao三层组织。适合两类人:第一次整合SSM、需要一份能照抄参考的新手;以及想把课程设计的系统架构讲给答辩老师听的在校生。我按“架构→搭建→实现→排错→验收”的顺序拆开讲,你能看到每个配置文件为什么这么写、每个接口怎么调、踩过的坑在哪里。

2. SSM系统架构与数据表设计:三个框架各管哪一段,表怎么建才不返工

一个常见的误解是:Spring MVC处理请求、MyBatis操作数据库,Spring只是把Service注册成Bean。实际上Spring的IOC容器把Controller层、Service层、Dao层对象串在一起,Spring MVC只在Web层做请求映射与参数绑定,MyBatis负责把SQL执行结果映射成Java对象。先把这个边界划清楚,后面配置文件的写法才不容易乱。

2.1 从JSP到数据库的请求流转:一次保存操作经过几个类

以“新增商品”为例,完整请求链路是:浏览器提交表单 → DispatcherServlet前置控制器 → HandlerMapping找到对应的Controller方法 → Controller调用Service接口 → Service实现类调用Mapper接口 → MyBatis执行XML里的SQL → 结果逐层返回 → 视图解析器渲染JSP页面。整条链路里,真正需要手写的只有三块:Controller方法、Service接口加实现类、Mapper接口加XML文件。

Controller层代码最典型的写法如下:

@Controller @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; @RequestMapping("/list") public String list(Model model, @RequestParam(defaultValue = "1") Integer pageNum) { PageResult<Product> result = productService.findByPage(pageNum, 5); model.addAttribute("result", result); return "product/list"; } }

逻辑说明:Controller在这里只做三件事,接收请求参数、调用Service、把返回结果放进Model供JSP渲染。@RequestParam(defaultValue = "1")表示URL上没有带pageNum参数时,默认按第1页处理,避免直接报400错误。

这里有个容易被忽略的约定:/product/list对应的是pageNum一个参数,如果你在JSP里用了分页组件,它可能传的是page或者currentPage,那么@RequestParam里的名字就要跟着改,否则前端怎么点翻页都没反应,还以为是SQL写错了。我在第一次写分页时就翻过车,前端传page=2,后端接口收pageNum,结果页面永远停在第一页。

Service层是业务规则的落点。同一套Mapper接口,可能在Service里被组合出不同含义,比如“查询商品列表”和“查询库存不足的商品列表”用的都是selectByCondition,差别在传入的查询条件对象。新手最容易犯的错是把业务判断写进Controller,比如在Controller里判断库存是否足够、抵扣余额后能不能下单。课程设计规模不大时看不出问题,但答辩时老师问“业务规则放哪一层”,答不上来就很尴尬。我的习惯是:Controller里只出现参数接收、Service调用、返回值处理,任何判断都下沉到Service。

2.2 数据表设计:商品、供应商、订单的最小子集

超市管理系统的课程设计,最少需要五张表,分别对应管理员、供应商、商品、订单、订单明细。表结构设计得合理,后面代码能省一半功夫;设计得不好,查询统计时就得写一堆冗长的子查询。

表名关键字段说明
t_userid, username, password, role管理员账号,role区分超级管理员和普通操作员
t_supplierid, name, contact, phone供应商信息,给进货单和商品表关联用
t_productid, name, price, stock, supplier_id, image商品表,supplier_id外键关联供应商
t_orderid, order_no, total_amount, create_time订单主表,存整笔订单的总金额和下单时间
t_order_itemid, order_id, product_id, quantity, price订单明细,一个订单对应多行明细

t_order和t_order_item必须分开,这是很多课程设计项目里“看起来能跑、实际很乱”的分水岭。如果只在订单表里放一个product_names字段,把多个商品名拼接成字符串塞进去,查询某个商品的销量时就得做字符串拆分,既低效又容易被扣分。拆成主表和明细表后,统计销量只需要对t_order_item做SUM(quantity) GROUP BY product_id,一句SQL就能解决。

建表时还要注意两个细节。第一,t_product.stock不要用int以外的类型,别设计成varchar然后在前端转数字,排序和加减库存都会出莫名问题。第二,外键supplier_id要加索引,课程设计数据量小看不出差距,但后续做供应商联动查询时,走索引和全表扫描的响应时间完全是两种体验。

2.3 各层类名与调用关系:命名对齐比你想的重要

三层架构的命名建议直接约定死:t_product表对应Product实体、ProductMapper接口、ProductMapper.xml、ProductService接口、ProductServiceImpl实现类、ProductController控制器。命名完全对齐后,有两个实际收益:一是Mapper接口和XML文件同名同目录,MyBatis扫描时不用额外配置映射关系;二是代码生成器在生成基础增删改查时,不会出现“接口改了参数、XML里还是旧SQL”的错位。

我见过一个项目,Dao接口叫ProductDao,XML叫ProductMapper.xml,配置里还忘了指定mapperLocations,结果项目启动时找不到SQL定义,排查半天才意识到是命名不一致导致的。命名对齐不只是好看,更是在减少黑匣子式的低效排错。只要约定统一,哪怕哪天XML文件丢了,看一眼接口名字就知道该去哪找。

3. 搭建SSM工程骨架:依赖版本、三个XML配置文件与Mapper扫描的绑定关系

SSM整合最耗时间的环节不是写业务代码,而是把Spring、Spring MVC、MyBatis三个框架拼起来。这一章我把Maven依赖、web.xml、spring-mvc.xml、applicationContext.xml、MyBatis配置逐项拆开,每个配置解决什么问题、写错位置会有什么后果,都会说清楚。

3.1 Maven依赖:版本对齐是第一道关卡

SSM是一个组合框架,版本冲突是所有问题的根源。以Spring 4.3.x搭配MyBatis 3.4.x为例,这是我做课程设计时最常用的一组,兼容性好,网上资料也多。Spring 5.x搭配mybatis-spring2.x需要Servlet 3.1以上版本支持,Tomcat 7以下会直接启动报错,课程设计没必要冒这个险。

<properties> <spring.version>4.3.18.RELEASE</spring.version> <mybatis.version>3.4.6</mybatis.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> </dependencies>

逻辑说明:spring-webmvc和spring-jdbc都依赖spring-core、spring-context等基础模块,Maven会自动传递,不需要逐个手动添加。mybatis-spring1.3.2是为Spring 4.3服务的,换到2.x就需要Spring 5,这是最常见的依赖冲突来源。

这里有一个容易反复踩的坑:mysql-connector-java版本必须和本机MySQL版本匹配。如果你本机装的是MySQL 8.x,还在用com.mysql.jdbc.Driver这个驱动类,连接池会报ClassNotFoundException。正确做法是升级驱动到8.0.x,并把驱动类改成com.mysql.cj.jdbc.Driver。我一般在jdbc.properties里同时保留两个注释行,环境切到MySQL 8时改一行驱动名加一行serverTimezone=Asia/Shanghai就行。

3.2 web.xml里的DispatcherServlet:启动顺序与映射规则

web.xml是Web应用的入口,里面需要配置两个关键东西:DispatcherServlet和CharacterEncodingFilter。DispatcherServlet负责接管所有请求,CharacterEncodingFilter解决POST乱码。

<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>

逻辑说明:forceEncoding=true表示不管请求本身带的编码是什么,都强制使用UTF-8,少了这个参数,POST提交的中文大概率乱码。load-on-startup必须写成1,否则Tomcat启动时不会初始化Spring容器,第一次访问页面才初始化,报错时机难判断。

url-pattern写/表示所有请求都交给DispatcherServlet处理,这样Controller里的@RequestMapping才能生效。但这也意味着JSP、CSS、JS等静态资源也会被拦截,所以spring-mvc.xml里必须配置静态资源放行,否则页面样式全丢,第5章我会专门展开这个坑。

3.3 applicationContext.xml与spring-mvc.xml:父子容器的分工

SSM配置文件的核心是父子容器概念。applicationContext.xml是父容器,管理Service、Dao、数据源、事务;spring-mvc.xml是子容器,只管理Controller和视图解析器。子容器可以引用父容器的Bean,父容器不能反向引用子容器。

<!-- applicationContext.xml --> <context:component-scan base-package="com.example.service"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- spring-mvc.xml --> <context:component-scan base-package="com.example.controller"/>

逻辑说明:父容器的扫描要排除@Controller,子容器的扫描只扫Controller包。这种分工是避免“一个Bean被创建两次”的关键。如果不做排除,Spring容器里会出现两个同名Service,@Autowired注入时可能因为歧义而报错,或者行为不一致,非常难排查。

这里还要加一个关键配置:在spring-mvc.xml里开启动态资源和注解驱动。

<mvc:annotation-driven/> <mvc:default-servlet-handler/>

annotation-driven负责注册RequestMappingHandlerMapping和RequestMappingHandlerAdapter,没有它,Controller上的@RequestMapping完全失效。default-servlet-handler把静态资源请求交回Tomcat默认Servlet处理,避免CSS、JS被DispatcherServlet拦掉。

3.4 MyBatis配置:Mapper扫描与XML路径的绑定

MyBatis整合Spring的关键是MapperScannerConfigurer,它扫描指定包下的Mapper接口,并为每个接口生成代理对象。这个配置写错位置,后果是启动不报错、运行时报空指针。

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.dao"/> </bean>

逻辑说明:basePackage填的是Mapper接口所在的包名,MyBatis会扫描该包下所有接口,把接口和同名XML文件绑定。这里隐含一个约定:ProductMapper.java必须和ProductMapper.xml同名且在同一包路径下。如果XML放在resources目录但没有建包目录,或者名字差一个字母,MyBatis会报Invalid bound statement (not found)。这个报错的意思是接口方法找到了,但SQL语句没有找到对应实现。

在同名同目录的约定下,MyBatis还会自动应用驼峰映射。数据库字段create_time对应实体属性createTime,只要在MyBatis配置里开启:

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

开启之后,查询结果就能自动把下划线字段名转成驼峰属性名,省掉大量resultMap。我不太建议在课程设计里手写resultMap映射所有字段,降级成一个resultType加上驼峰映射,代码量少一半,出错概率也低很多。

4. 核心功能实现:登录拦截、商品管理与前端交互

框架搭好之后,剩下的就是业务功能怎么写了。登录拦截、商品增删改查、分页模糊查询,这三个功能基本覆盖了超市管理系统的主线,写完它们,订单和供应商模块的套路完全一样。

4.1 登录拦截器:别把静态资源也拦了

登录状态校验是课程设计的必考点,最常见方案是写一个HandlerInterceptor,在请求进入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.jsp"); return false; } return true; } }

逻辑说明:preHandle返回false时,请求在拦截器处终止,不会到达Controller。loginUser是登录成功时往Session里放的对象。判断为空就重定向到登录页。

拦截器注册在spring-mvc.xml里:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/css/**"/> <mvc:exclude-mapping path="/js/**"/> <mvc:exclude-mapping path="/images/**"/> </mvc:interceptor> </mvc:interceptors>

逻辑说明:mapping path="/**"表示拦截所有请求,exclude-mapping放行登录接口和静态资源目录。/login是登录Controller的URL,如果不放行,就会出现“还没登录连登录页都进不去”的逻辑死循环。

这里的常见误用是只拦截*.action或/pages/**,漏掉了JSP直接访问的场景。实际浏览器里直接输/product/list.jsp也能绕过拦截器,因为JSP请求默认不走Spring MVC。所以要么把JSP页面全部放在WEB-INF/views下并只通过Controller跳转,要么拦截路径写成/**再显式放行登录和静态资源。我一般选择后者,改动最小且不容易漏。

4.2 商品管理增删改查:Controller传参的细节

商品保存是典型的表单提交场景。表单里有商品名、价格、库存、供应商下拉框、图片路径等字段,Spring MVC可以直接把同名表单字段封装进Product实体。

@PostMapping("/save") public String save(Product product, @RequestParam("supplierId") Integer supplierId) { productService.save(product); return "redirect:/product/list"; }

逻辑说明:Spring MVC在做参数绑定时,会自动调用Product的setter方法,把请求参数按同名规则填入对象属性,不需要手写request.getParameter。@RequestParam("supplierId")单独接收下拉框选中的供应商编号,因为表单里这个参数名和Product的属性名不一定一致。

redirect:前缀是做“保存后重定向”的关键。如果直接返回"product/list"字符串,等于转发到列表JSP,但浏览器地址栏还是/product/save。刷新页面时表单会被重复提交,订单和商品会出现两条重复数据。用redirect:/product/list让浏览器重新发起GET请求,刷新页面就没有副作用了。

有一个细节需要提醒:Product里如果还有图片上传字段,multipart/form-data类型需要额外配置MultipartResolver:

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> </bean>

maxUploadSize单位是字节,10485760即10MB,超过这个大小会抛异常。不配置这个Bean,@RequestParam("file") MultipartFile会直接报空指针或400错误。

4.3 分页与模糊查询:参数封装与SQL拼接的简化方案

分页和模糊查询是系统展示页最常见的组合需求。前端传pageNum、pageSize和keyword三个参数,后端计算偏移量,在MyBatis里动态拼接SQL条件。

先看Controller和Service:

@RequestMapping("/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "5") Integer pageSize, @RequestParam(required = false) String keyword) { int offset = (pageNum - 1) * pageSize; PageResult<Product> result = productService.findByPage(offset, pageSize, keyword); model.addAttribute("result", result); return "product/list"; }

这里有一个容易搞错的换算关系:数据库LIMIT的偏移量从0开始,前端页码从1开始。所以offset = (pageNum - 1) * pageSize。如果直接把pageNum传给LIMIT,第一页正常,第二页会跳过一条本该显示的数据,属于典型的“差一”错误。

再来看XML里的动态SQL:

<select id="findByPage" resultType="com.example.entity.Product"> SELECT * FROM t_product <where> <if test="keyword != null and keyword != ''"> AND name LIKE CONCAT('%', #{keyword}, '%') </if> </where> ORDER BY id DESC LIMIT #{offset}, #{size} </select>

逻辑说明:<where>标签会自动去掉条件开头多余的AND,这样即使keyword为空,生成的SQL是SELECT * FROM t_product ORDER BY id DESC而不是SELECT * FROM t_product WHERE ORDER BY。<if>标签判断传入的查询条件,名字为空时忽略该条件。CONCAT('%', #{keyword}, '%')是MySQL安全的字符串拼接写法,同样条件下用'%${keyword}%'会有SQL注入风险,不建议。

使用#{}和${}的边界也在这里:#{}是预编译占位符,MySQL会转义传入值;${}是直接拼接字符串,值里出现单引号就可能导致SQL语法错误甚至注入。课程设计里如果图省事全部用${},答辩时被问到SQL注入问题会很被动。我的原则是:所有条件值都用#{},只有表名、排序字段等结构性的东西才考虑${},并且要做白名单校验。

5. SSM超市管理系统避坑与排查:五个翻车现场(必看)

框架整合类项目的高频错误高度集中,我把最常见的五类问题按“现象→原因→解决”整理出来。每一类都付出了实际调试时间,照着排查省一半时间。

5.1 启动后访问404:DispatcherServlet把静态资源吞了

现象:项目能在Tomcat启动,数据库连接也正常,但访问登录页时CSS样式全丢,直接访问Controller的URL能出页面,访问/js/jquery.js却404。

原因:DispatcherServlet配置时url-pattern用了/,把静态资源请求也拦截了。Spring MVC没找到对应的Controller方法,返回404。

解决:在spring-mvc.xml里加一行<mvc:default-servlet-handler/>。加上以后Spring MVC会把没有匹配到Controller的请求交回Tomcat默认的Servlet处理,CSS、JS、图片正常返回。这个配置丢失的时候后台不报错,第一次遇到基本靠猜。

5.2 查询结果返回null:Mapper接口有方法但XML没绑上

现象:代码运行没异常,Service调Mapper接口时也能进入方法,但返回的List<Product>里元素是null,或者直接报Invalid bound statement (not found): com.example.dao.ProductMapper.findByPage。

原因:接口方法名和XML的<select>的id不一致,或者XML文件位置不对。MyBatis通过接口全限定名去匹配同名XML,如果ProductMapper.xml放到了resources/com/example/dao/以外的地方,或者文件名差一个字母,就找不到SQL。

解决:先检查target目录有没有编译后的XML文件。IDEA里常见情况是XML忘在src/main/java目录下,但构建时没有把XML复制进classes目录。我在pom.xml里一般会加一段构建配置,确保XML和properties文件随编译输出:

<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> </build>

这段配置的含义是:src/main/java和src/main/resources下的XML文件都参与构建。不少课程设计项目把Mapper XML放在Java包目录下,不加这段配置,部署后就是上面那个报错。

5.3 中文乱码:POST、GET、数据库三个环节逐个排查

现象:新增供应商时输入中文名,保存后页面显示???,但列表页其他数据正常。有时同一个操作POST是乱码,GET却没乱码。

原因:乱码可能是三个环节中的任意一个造成的。POST请求时web.xml没有配CharacterEncodingFilter;数据库连接URL没有指定characterEncoding=utf8;或者JSP的pageEncoding和表字段的字符集不一致。

解决:按顺序排查。第一步,确认web.xml里的编码过滤器配了forceEncoding=true,只设encoding=UTF-8不设force时,已解析的请求体不会重新编码。第二步,确认jdbc.properties里URL带了useUnicode=true&characterEncoding=utf8。第三步,用Navicat或命令看表结构,确认表字段是utf8或utf8mb4,不是latin1。我自己习惯把CharacterEncodingFilter的url-pattern写成/*而不是/,区别在于/*能过滤到包括JSP在内的所有请求,避免JSP响应里的中文也踩坑。

5.4 新增商品后回显的id是null

现象:插入商品成功后,代码里调用product.getId()拿到的是null,后续要用这个id插入订单明细时直接空指针。

原因:数据库主键使用了自增,但MyBatis不知道插入成功后要回填自增主键。如果XML里没有配置useGeneratedKeys,MyBatis只执行插入,不返回任何自增值。

解决:insert标签上显式声明:

<insert id="insertProduct" useGeneratedKeys="true" keyProperty="id"> INSERT INTO t_product(name, price, stock, supplier_id) VALUES(#{name}, #{price}, #{stock}, #{supplierId}) </insert>

useGeneratedKeys="true"让JDBC的getGeneratedKeys()生效,MySQL会把自增id回写到Product实体的id属性上。keyProperty指定的是Java实体属性名,不是数据库列名。如果这里写成keyColumn="id"也能用,但keyColumn的语义不同,多表关联时才建议使用。

5.5 前端表格渲染出undefined:JSON字段名对不上

现象:浏览器Network里能看到后端返回JSON,结构也正常,但页面表格里全是undefined,控制台报Cannot read properties of undefined。

原因:前端取的字段名和后端实体属性名不一致。常见于数据库字段是create_time,实体属性是createTime,后端返回的JSON key是createTime,前端却写了create_time。或者是后端返回的是List<Map<String, Object>>,Map的key大小写和前端不一致。

解决:这是纯前端问题,先去Network看返回JSON的实际key名,再改前端取值或调整后端返回结构。我的习惯是列表页面不做直接实体输出,Controller返回一个封装好的Map或DTO,字段名显式指定,和前端约定好。比如商品列表需要展示供应商名称,Product实体里没有这个字段,就在DTO里加一个supplierName字段,避免前端硬拼两个接口的数据。

如果上面五个问题都排查完还没解决,最后再做三件事:第一,看Tomcat catalina日志里最近一次完整的异常堆栈,定位到具体行号;第二,把target目录删掉重新mvn clean package,排除旧的class和XML文件残留;第三,检查IDEA的Maven配置是否指向本机仓库,换过JDK或仓库路径后经常出现依赖加载不全。这三步走完,九成以上的环境问题都能浮出水面。

6. 从“能跑”到“能讲”:验收前的验证清单与两个改动建议

课程设计答辩时,老师看得最多的不是代码量,而是演示流程是否完整。我建议在提交前走一遍全流程自测,每一步都有明确预期,整个过程约八分钟。

6.1 八分钟自测路径

步骤操作预期结果
1启动Tomcat,访问登录页页面样式正常,无404
2输入管理员账号登录跳转到首页,Session写入登录用户
3新增一个商品并上传图片列表第一条出现该商品,中文显示正常
4编辑该商品价格列表价格即时更新
5按关键字模糊搜索只返回匹配商品,分页可翻到第二页
6为商品新增一笔进货订单商品库存数量增加
7退出登录跳回登录页,保留登录前页面
8未登录直接访问商品列表URL被拦截器重定向到登录页

第8步是拦检查拦磏器的关键验证。很多项目登录后一切正常,但退出后直接在浏览器输入/product/list还能访问,说明拦截器漏配了路径,这是答辩现场最容易被指出来的问题。

6.2 两个低成本改动

如果时间充裕,我建议做两个低成本增强,它们的工作量都不大,但能让系统看起来更有“业务完整度”。

第一个是供应商联动的Ajax查询。在订单页面选供应商后,通过getJSON请求后端/supplier/{id}/info,把联系人、电话显示在旁边输入框。这个改动只需要在Controller加一个@ResponseBody方法,前端加十来行jQuery代码。它证明你理解了前后端数据交互,而不只是会return "jsp"。

第二个是库存预警。给t_product加一个warning_stock字段,商品列表Page渲染时判断stock <= warning_stock,行背景标成浅红色。SQL里不需要任何改动,Mapper查询时把两个字段都查出即可,前端JSP里加两个c:if标签就完成。这个功能是超市管理的典型业务场景,答辩时能自然讲出“低库存会阻碍销售”这类业务价值。

我第一次做课程设计时,为了赶进度跳过了拦截器配置,结果答辩现场演示时直接从列表页点进去,被老师点了一句“你没登录怎么进来的”。从那以后我每次做完系统,都强制自己从登录页重新走一遍全流程,验证完再交付。那个教训让我养成一个习惯:任何SSM项目改完代码,第一件事不是看功能,而是先验证登录跳转和权限拦截。这份资源里同样包含完整的数据库脚本和配置注释,希望这篇拆解能帮你少踩几个坑,动手跑一遍,比干看文章有用得多。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进