1. 为什么“SSM + 商店管理系统”总是摆在毕设选题列表第一名
1.1 这个题目到底想考你什么
每年一到毕业季,总有同学私信问我:“师兄,我在选题系统里又看到了 SSM 商店管理系统,还带源码和论文,这东西到底好不好做?”说实话,单看名字它很普通,但它在本科阶段的 Web 项目里属于“恰到好处”的难度。它不像“图书管理系统”那样一眼到底,也不像“分布式电商平台”那样脱离课程实际。它有用户、有商品、有订单、有库存、有统计报表,刚好能把 Java 基础、数据库设计和 Web 框架全部串起来。
尤其对于 2026 届的同学来说,SSM 组合依然是学校教学里最常讲的 Spring 生态雏形。Spring 负责对象管理,SpringMVC 负责请求路由,MyBatis 负责数据库操作,整套技术栈的学习曲线相对平缓。商店管理系统则提供了足够多的业务场景:商品分类、库存预警、订单结算、会员积分、营业统计,甚至还可以扩展出权限管理、Excel 导出、条形码生成。这些功能听起来不难,但每一个背后都对应着明确的数据库表设计和接口逻辑,老师一看就知道你有没有真正动手写过代码。
我也见过不少同学觉得“这个题太老”。但毕业设计的核心评价标准从来不是技术多么新,而是你能否完整地实现一个可用系统,并且能说清楚里面每一步的原理。SSM + 商店管理系统是少有的“老但不过时”的组合,它在代码量、业务复杂度、论文篇幅之间找到了平衡。如果你能把这套题吃透,不管是答辩还是后续自学 Spring Boot,底子都会很扎实。
1.2 一套 SSM 里到底藏了哪些东西
很多非科班或者基础不牢的同学,听到“SSM”会下意识觉得它是个大框架。实际上它就是三个开源框架的组合,各管一段。我们拿商店收银的例子来打比方:顾客进店下单,前台的收银员接待顾客,这相当于 SpringMVC 的 Controller 层,负责接收页面请求并返回结果;后厨和仓库调配货物,这相当于 Spring 管理的 Service 层和 DAO 层,负责处理业务流程;仓库管理员记录进出账,这相当于 MyBatis 持久层,负责把数据落到数据库里。而 Spring 容器本身,则像是整个门店的运营制度,把所有员工和岗位统一管起来。
在实际代码里,Spring 的 IOC 让你不再通过 new 来创建对象,而是用注解把对象交给容器管理;AOP 又允许你在执行某些方法前后统一记录日志或开启事务。SpringMVC 接收前端传来的参数,通过注解映射到处理方法,然后把业务数据和视图名称一起返回。MyBatis 则让你把 SQL 写在 XML 或注解中,避免手动写大量的 JDBC 代码。这三块拼起来,正好形成一个完整请求链路:页面发请求 -> DispatcherServlet -> Controller -> Service -> Mapper -> 数据库,再反向封装返回 JSON 或 JSP 页面。
我这里给基础薄弱的同学一个建议:不要在还没有理清请求链路时就去死磕源码实现。你只需要记住一次完整的登录操作经过了哪几个方法、每个方法的职责是什么,这比背诵任何八股文都管用。
1.3 选型时最常见的几个误区
第一类误区是“一上来就换成 Spring Boot”。如果你准备时间充足,改用 Spring Boot + MyBatis 自然更省事,它内置了 Tomcat、自动配置,搭建时间少一大半。但问题在于,很多学校的大纲和答辩评审还是按 SSM 的架构来提问。你可以在项目中引入 Spring Boot,然后说它是 Spring 的快速封装,但必须能解释清楚底层和 SSM 的对应关系。如果你连基础配置都说不清,反而容易被追问到尴尬。
第二类误区是完全依赖图形化工具生成代码。现在很多代码生成器能直接生成 Mapper、Controller、前端页面。生成的代码确实能跑,可如果答辩时老师让你现场改一个需求,比如把“单商品库存扣减”改成“批量库存扣减”,你如果对着代码无从下手,那问题就出在“没理解”。我不是反对用工具,而是建议你先手写一遍核心的 Controller 和 Service,然后用工具去补齐重复的 CRUD,这样既节省时间又能保证自己hold住代码。
第三类误区是把论文和系统完全分开做。很多同学先把代码写完,最后三天憋出一篇论文,结果系统里的表名和论文里的图对不上。正确做法是先确定功能模块,再按模块同时推进代码和文档,至少要保证数据库设计、核心流程图、界面截图这三部分是从项目里真实提炼出来的。后面我会专门说论文怎么写,这里先记住一个原则:论文是系统的说明书,不是独立的文学作品。
2. “商店管理系统”的需求远不止写几个 CRUD
2.1 干需求之前,先回答四个问题
做毕设最容易犯的毛病就是拿到题目就建表。建表前你得先问自己四个问题:这个商店卖什么?谁来使用系统?用户需要被区分成几种角色?每天的订单量和数据量大概有多大?这四个问题直接决定了表结构、权限逻辑、分页方案和统计报表怎么写。
以最常见的超市便利店场景为例,角色通常分为管理员、收银员、仓管员。管理员负责员工账号、商品上下架、查看营业报表;收银员负责前台开单、收款、退货;仓管员负责入库、出库、库存调整。这三个角色对应的是不同菜单和不同按钮权限,而不是简单用一个 isAdmin 字段区分。你可以在用户表中加一个 role_id,再在菜单表中用角色关联菜单,或者更简单一点,在拦截器或过滤器里按角色名写判断逻辑。毕业设计不要求做到细粒度权限,但要能拦住普通收银员访问管理员页面。
至于卖什么,很多人会写成“商品信息管理”,实际上商店商品一般有两种方式:一种是固定 SKU,比如每瓶饮料有自己的条码、进价、售价;另一种是称重或按件计费,需要临时改价。如果你选了生鲜超市作为背景,最好在商品表中增加一个 goods_no(货号)、bar_code(条码)字段,这样论文场景会更有说服力。
2.2 把功能列表拆成“能演示”的样子
需求阶段不要只顾着罗列功能,而是要想清楚答辩时怎么演示。我习惯把功能分成三类:基础 CRUD、核心业务、加分亮点。基础 CRUD 包括商品增删改查、分类管理、供应商管理、客户管理;核心业务包括商品入库出库、库存变动、订单创建、订单结算、退货退款;加分项包括库存预警、月度销售统计、会员积分、Excel 导入导出。三级划分的好处是,答辩时间不够时你至少能把核心业务完整走一遍。
商店系统的关键词里,“库存”“订单”“统计报表”通常权重最高。建议把这些功能做深而不是做多。比如库存预警不能只是查询时显示一下库存低于多少,而应该在商品列表页用颜色标出低库存商品,或者在登录首页做一个待处理提醒模块。很多同学喜欢堆功能,页面做了十几个,但每个页面都只有一个简单的表格,这样反而显得浅。
下面是一个我给同学做需求梳理时常用的功能对照表,大家可以直接拿来做选题分析:
| 功能模块 | 包含页面/操作 | 对应角色 | 是否为核心必做 |
|---|---|---|---|
| 登录与权限 | 账号密码登录、修改密码、退出登录 | 全部 | 必做 |
| 商品分类 | 分类树维护、上下级分类 | 管理员 | 必做 |
| 商品管理 | 商品列表、新增、编辑、上下架 | 管理员、仓管员 | 必做 |
| 库存管理 | 入库、出库、库存调整、预警 | 仓管员 | 必做 |
| 订单管理 | 创建订单、订单列表、订单详情、退款 | 收银员、管理员 | 必做 |
| 会员管理 | 会员开卡、积分累计、等级折扣 | 收银员 | 加分 |
| 统计报表 | 日/月营业额、TopN商品、分类占比 | 管理员 | 加分 |
| 系统管理 | 用户管理、角色管理 | 管理员 | 加分 |
2.3 页面设计要提前想好,别等后端写完
很多同学后端的接口写得很顺利,到了前端开始卡壳。SSM 项目最常用的前端方案是 JSP + JSTL + Bootstrap,也有用 Vue 前后端分离的。如果是传统 JSP 方案,每个页面都要写好表单和表格,列表页注意翻页、搜索条件、操作按钮三个区域要固定存在。新增和编辑可以做成模态框,也可以在同一个 jsp 里用 if 判断区分新增编辑模式。推荐使用模态框,因为这样页面数量少,演示时也更流畅。
如果选了 Vue 分离式开发,记得把静态页面先放在前端项目中,通过 Axios 调后端接口。不过要提醒一句,许多实训基地和答辩现场只提供内网环境,端口配置容易出错,所以前后端分离项目一定要提前采购并使用 Vite 或 Webpack 打包后的 dist 文件,并配置好后端的静态资源映射。这块内容看似和 SSM 无关,却往往是项目跑不起来的原因。
3. 数据库设计是最容易被低估的部分
3.1 核心表怎么拆分,直接决定了代码难不难写
商店管理系统的数据库一般不少于 8 张表。我见过的最精简版本有 5 张表:用户、商品分类、商品、订单、订单明细。如果选这个精简版,代码会写得很痛苦,因为供应商、库存日志、权限这些功能全部要往现有表里塞外键或字段。建议至少拆到 10 张表,但又不要为了加表而加表,每一张表都必须有明确的业务含义。
下面是核心表的基本拆法:
sys_user 用户表:user_id, username, password, real_name, role_id, status, create_time。密码不要直接明文存,哪怕毕设也应该用 MD5 加盐或 BCrypt 加密。答辩老师看到明文密码会非常减分。
category 商品分类表:category_id, parent_id, name, sort_order, status。
product 商品表:product_id, category_id, product_no, bar_code, name, price, stock, sale_count, status, create_time, update_time。
supplier 供应商表:supplier_id, name, contact, phone, address。
stock_log 库存流水表:log_id, product_id, change_type, change_count, before_stock, after_stock, remark, operator_id, create_time。这张表强烈建议保留,它是“库存操作留痕”的关键,论文里也能单独写一节。
member 会员表:member_id, name, phone, points, level, balance, create_time。
order 订单表:order_id, order_no, member_id, user_id, total_amount, pay_amount, discount_amount, pay_type, status, create_time, pay_time。
order_item 订单明细表:item_id, order_id, product_id, product_name, price, count, amount。
3.2 订单明细为什么必须单独建表
很多新手写订单时喜欢在订单表里放一个 goods_ids 字段,用逗号拼接商品 ID;或者干脆只保存总金额,明细不落库。这两种做法在毕业设计中都会给答辩带来麻烦。单独建立 order_item 表后,每张订单对应多条明细,不仅符合数据库第一范式的直觉,而且方便统计商品的销售数量,后续做“热销商品排行榜”时直接按 product_id 分组求和,效率很高。
订单表里的 order_status 建议用整型表示状态,比如 0 表示待付款,1 表示已付款,2 表示已发货/已完成,3 表示已取消,4 表示已退货。不要用字符串像“未支付”“已支付”,这样在 Java 代码里一堆中文硬编码很容易出错。前后端的字典值可以用状态枚举类统一管理,或者在前端页面用 JSTL 的 c:if 判断。
3.3 外键、索引和软删除,毕设里如何取舍
理论上外键能保证数据一致性,但在 SSM + MyBatis 项目中,大量外键反而会影响开发效率,因为删除主表数据时总是被外键约束拦住。我的建议是:在表结构中保留逻辑关联,但不一定都建物理外键,核心业务表之间的关系用索引和业务代码来保证。比如商品表的 category_id,既然不会随便改,可以建一个普通索引用于按分类查询。而在代码层面,删除一个分类前必须先查该分类下是否有商品,如果有则提示“分类下还有商品,不能删除”。
软删除也很重要。商品表不要直接 DELETE,而是加一个 status 或 is_deleted 字段,0 正常,1 删除。这样库存流水和历史订单在查询时不会因为商品被删而丢失关联。很多系统在演示时删掉一个商品,再翻历史订单发现商品名称空了,就是因为没有软删除再加上外键没有做冗余存储。
3.4 给出可直接套用的建表参考
下面这段 SQL 是商品表和库存流水表的最小可用版本,你可以直接拿去做原型验证。注意我给时间字段加了默认值,Java 里就可以不用每次手动塞时间。
CREATE TABLE `product` ( `product_id` INT NOT NULL AUTO_INCREMENT COMMENT '商品ID', `category_id` INT DEFAULT NULL COMMENT '分类ID', `product_no` VARCHAR(32) NOT NULL COMMENT '商品编号', `bar_code` VARCHAR(64) DEFAULT NULL COMMENT '条码', `name` VARCHAR(100) NOT NULL COMMENT '商品名', `price` DECIMAL(10,2) NOT NULL COMMENT '零售价', `cost_price` DECIMAL(10,2) DEFAULT NULL COMMENT '进价', `stock` INT NOT NULL DEFAULT 0 COMMENT '当前库存', `warn_stock` INT NOT NULL DEFAULT 10 COMMENT '库存预警阈值', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1上架 0下架', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`product_id`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'; CREATE TABLE `stock_log` ( `log_id` INT NOT NULL AUTO_INCREMENT, `product_id` INT NOT NULL, `change_type` TINYINT NOT NULL COMMENT '1入库 2出库 3盘盈 4盘亏 5销售扣减', `change_count` INT NOT NULL, `before_stock` INT NOT NULL, `after_stock` INT NOT NULL, `remark` VARCHAR(255) DEFAULT NULL, `operator_id` INT DEFAULT NULL, `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`log_id`), KEY `idx_product` (`product_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表';这里强调一点,库存流水表里要同时存 before_stock 和 after_stock,而不是只存一个 change_count。这在复盘问题时非常有用,学员后面问“为什么库存不对”,你一看流水就知道是哪一步加减出了问题。
4. 后端核心逻辑:登录认证、库存扣减与订单事务
4.1 登录认证到底该不该用 Session
SSM 项目最常见的登录方案是使用 HttpSession。用户登录成功后把 user 对象放入 session,然后通过 SpringMVC 拦截器判断用户是否登录,以及当前访问的 URL 是否在权限范围内。这种方案实现简单,而且和 JSP 的 session 作用域天然契合。但如果使用了前后端分离,Session 方案需要配置跨域携带 Cookie,相对麻烦。这时候可以改用 Token 方案,登录成功后生成一个 UUID 或 JWT 返回给前端,前端每次请求放在请求头里,后端通过拦截器解析。
我个人的建议是,如果你用的是 JSP + Bootstrap,无脑选 Session。因为答辩老师最常问的就是 Session 和拦截器的关系。代码里只需要一个配置文件,再写一个实现 HandlerInterceptor 的拦截器类,表示没有登录时重定向到 login.jsp。这里注意 SpringMVC 中放行登录接口和静态资源的配置,否则会出现登录后刷新页面 JS 和样式全丢失的现象。
4.2 库存扣减的一条 SQL 胜过“先查再改”
现在到了商店管理系统最关键的一道坎:创建订单时如何扣库存。初级写法是先根据商品 ID 查出库存,判断库存是否充足,然后 UPDATE 商品表 set stock = stock - count。这种写法在并发量低时没问题,但它不是原子操作。如果两个请求同时读到 stock=5,各自都认为库存够,结果都去执行 set stock = stock - count,在极端情况下会出现超卖。虽然毕设演示没有并发压力,但我一直建议直接用原子更新。
对应的 MyBatis Mapper 方法可以写成这样:
@Update("UPDATE product SET stock = stock - #{count} " + "WHERE product_id = #{productId} AND stock >= #{count}") int reduceStock(@Param("productId") Integer productId, @Param("count") Integer count);这段 SQL 返回受影响行数 affected。如果 affected 等于 0,说明库存不足或者商品不存在,Service 层直接抛异常并提示用户。这样既不回退库存,也不需要加锁,逻辑非常干净。同理,增加库存也直接用 stock = stock + #{count},配合库存流水表记录变更原因。
4.3 一个订单从创建到落库的事务控制
订单业务同时要操作多张表:插入订单主表、插入订单明细、扣减商品库存、记录库存流水、计算会员积分。这些操作要么全部成功,要么全部失败,所以 Service 方法上要加 @Transactional。下面是一个简化版代码:
@Service public class OrderServiceImpl implements OrderService { @Resource private ProductMapper productMapper; @Resource private OrderMapper orderMapper; @Resource private OrderItemMapper orderItemMapper; @Resource private StockLogMapper stockLogMapper; @Override @Transactional(rollbackFor = Exception.class) public Order createOrder(OrderDTO dto) { // 1. 创建订单主记录,状态为待付款 Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setMemberId(dto.getMemberId()); order.setUserId(dto.getUserId()); order.setTotalAmount(dto.getTotalAmount()); order.setStatus(0); orderMapper.insert(order); // 2. 遍历商品明细,扣减库存并插入明细 List<OrderItemDTO> items = dto.getItems(); BigDecimal total = BigDecimal.ZERO; for (OrderItemDTO item : items) { int affected = productMapper.reduceStock(item.getProductId(), item.getCount()); if (affected == 0) { throw new RuntimeException("商品ID " + item.getProductId() + " 库存不足"); } Product product = productMapper.selectById(item.getProductId()); OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getOrderId()); orderItem.setProductId(product.getProductId()); orderItem.setProductName(product.getName()); orderItem.setPrice(product.getPrice()); orderItem.setCount(item.getCount()); orderItem.setAmount(product.getPrice().multiply(BigDecimal.valueOf(item.getCount()))); orderItemMapper.insert(orderItem); // 3. 写库存流水 int afterStock = product.getStock() - item.getCount(); stockLogMapper.insert(buildStockLog(product, 5, item.getCount(), afterStock)); total = total.add(orderItem.getAmount()); } // 4. 最后更新订单总金额 order.setTotalAmount(total); orderMapper.updateAmount(order.getOrderId(), total); return order; } private String generateOrderNo() { // 一般取时间戳 + 随机数,或者用雪花算法 return System.currentTimeMillis() + String.valueOf((int)((Math.random() * 9 + 1) * 100000)); } }注意 @Transactional 默认只在抛出 RuntimeException 时候回滚,所以你可以在 Service 方法里直接 throw new RuntimeException("库存不足"),这样事务会回滚。如果你们习惯用自定义业务异常,记得设置 rollbackFor = Exception.class,避免某些检查异常没有被回滚。
4.4 列表查询和分页别手写 Limit
商品列表、订单列表这些页面几乎必然需要分页。SSM 项目里最简单的方式是引入 PageHelper 插件,在 MyBatis 配置文件里注册插件,然后查询前调用 PageHelper.startPage()。它的原理是拦截即将执行的 SQL,自动拼接 LIMIT 语句,并且用 PageInfo 包装查询结果,方便前端拿到总条数和页码。
PageHelper.startPage(pageNum, pageSize); List<Product> list = productMapper.selectProductList(dto); PageInfo<Product> pageInfo = new PageInfo<>(list);这里有个容易翻车的细节:PageHelper 是线程本地变量,所以 startPage 必须紧跟紧接着的 Mapper 查询语句,中间不能插入别的查询。如果你在两个查询之间调用了一个无关的 Mapper,分页会被第二个查询吃掉。我见过不少同学调试半天发现 List 数据总是多出来,就是因为这个原因。
5. 从源码结构到论文组织,一条线走通
5.1 一份能过查重又能落地的源码结构
拿到“源码+论文”这种题目时,最忌讳的是你只会在 IDE 里启动项目,论文却不知道代码为什么这么分层。SSM 项目的标准分层通常长这样:
src/main/java com.example.shop controller -- 接收请求,参数校验 service -- 业务接口 service.impl -- 业务实现 mapper -- MyBatis 接口 entity -- 实体类 dto -- 前端传参封装 vo -- 页面显示值对象 interceptor -- 登录/权限拦截器 config -- 配置类 src/main/resources mapper -- Mapper XML 文件 spring -- 或 applicationContext.xml springmvc.xml jdbc.properties src/main/webapp WEB-INF views -- JSP 页面 web.xml static -- 静态资源这种分层最大的好处是代码职责清楚。Controller 里只做参数接收和调用 Service,不要写业务 SQL;Service 实现类里写事务逻辑;Mapper 只做单表或常用多表查询。很多源码下载包里的代码,Controller 里直接注入 Mapper,甚至把多条 SQL 逻辑写到了 Controller,看起来能跑,但答辩时会被老师问“业务逻辑都堆在控制层,你怎么测?”。所以拿到源码后如果发现这种问题,第一件事是重构,而不是改个名字就提交。
5.2 论文的章节不要复制网上的目录
论文结构不用太创新,但一定要和你自己的系统对应。常见的毕业设计论文章节是:绪论、相关技术、需求分析、系统设计、系统实现、系统测试、总结。重点在系统设计和系统实现这两章要把实际表结构和功能模块展开写。
我建议论文里的每个功能模块配一张“页面截图 + 核心代码片段”,代码不要整页贴,只贴关键方法。比如第四章“商品库存预警模块设计”里,可以先描述需求:当库存低于预警阈值时,首页统计栏显示异常数量,商品列表用红色背景标出。然后贴一段对应的 Service 方法,再贴一张实际页面截图。这样评审老师读起来会很轻松,也会认为你的工作量真实。
系统测试不要只写“功能测试通过”六个字。要写出测试用例表,比如用例编号、测试步骤、预期结果、实际结果。哪怕只是手工测试,一张 10 行的用例表也比一句话强得多。性能测试可以写“使用 JMeter 模拟 50 个并发请求,登录接口平均响应时间 120ms”,但这部分必须在你真实跑过之后才写,不然答辩一问具体数据你就露馅。
5.3 页面设计里最容易提升观感的几个点
商店管理系统最容易给人“普通 CRUD”的感觉,是因为界面只有表格和按钮。想提升观感,不需要你精通前端,只需要做好几件小事:统一列表页的搜索条件放在搜索栏里,新增编辑按钮放在右上角,操作按钮用不同颜色;商品价格用 Decimal 格式化,金额列保证两位小数;当前选中菜单高亮;表格空数据要显示友好的空状态。
还有一个性价比很高的功能:首页仪表盘。登录后默认展示今日订单数、今日营业额、库存预警数、会员总数,下面配一个按最近 7 天销售额生成的柱子图。做这种图不一定要引入 ECharts 系列,你可以在 JSP 里用 Chart.js,引入一个 JS 文件,写一个 ajax 请求获取统计数据,再渲染 canvas 即可。就一个首页统计,答辩时整个项目的感觉都不再是“普通管理系统”。
6. 运行部署和调试时,那些没人提前告诉你的坑
6.1 环境版本不匹配是头号杀手
SSM 是老牌组合,对版本很敏感。最常见的组合是 JDK 1.8、Maven 3.6、Tomcat 8/9、MySQL 5.7。如果你用了 JDK 17 且没有处理模块化相关设置,实际项目会抛出 IllegalAccessError;如果 MySQL 驱动用了 8.x,而你的 jdbc.properties 里还写着 com.mysql.jdbc.Driver,就会提示找不到驱动类。现在的正确写法是:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/shop_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=yourpassword很多同学第 1 次启动就卡在数据库连接不上。先把 MySQL 服务启动,然后用命令行连接一下数据库,最后再启动 Tomcat。千万不要直接跳过去看浏览器报错页面,Tomcat 的报错日志通常只告诉你数据库连接异常,而真正的根因在控制台完整堆栈的第一行。
6.2 前端 404 和中文乱码的排查路径
SpringMVC 静态资源 404 是非常经典的问题。如果你把 JS、CSS、图片放在 webapp/static 下面,需要在 springmvc.xml 里配置放行:
<mvc:resources mapping="/static/**" location="/static/"/>同时,前端 JSP 页面里引用静态资源,最好使用带项目名的绝对路径,比如:
<link href="${pageContext.request.contextPath}/static/css/style.css" rel="stylesheet"> <script src="${pageContext.request.contextPath}/static/js/jquery.min.js"></script>这样不会因为双击打开页面或者跳转到子路径导致资源路径错乱。中文乱码则要看三个地方:MySQL 连接 URL 是否带 characterEncoding=utf8,后端过滤器的编码是不是 UTF-8,数据库表的字符集是否为 utf8mb4。三个地方有一个漏了就会乱码。
6.3 拿着源码跑通后,一定要自己动手改三个地方
不管你是自己从零写的,还是参考了某个开源版本,我强烈建议跑通之后立刻做三件改造:第一,把超级管理员账号的密码改成加密存储并修改默认密码;第二,把订单号生成逻辑改成时间戳加随机数的形式;第三,给商品列表加一个“库存预警”查询条件。这三处改造小但能说明你真正看懂了代码,就算答辩老师现场问,你也能答上来。
我辅导过不少同学,他们一开始拿到的参考代码里管理员密码是明文 123456,登录后甚至可以直接在 URL 上访问 /admin/product/add。这些安全上的问题在代码审阅时都会被放大。你不需要做成银行级别的安全系统,但至少不能有低级漏洞。
7. 答辩前一天的检查清单与我的几点体会
7.1 答辩演示时的“保命顺序”
第七天答辩,第六天晚上你还不知道怎么演示才不会翻车,这是常态。我的建议是准备一份“演示剧本”,按照下面顺序走:先打开首页统计面板,展示今日营业额和库存预警;然后进入商品管理,添加一件商品,把库存设为 1;去库存管理中做一次入库;回到商品列表确认库存更新;创建一笔订单,选择两件商品,确认支付;打开订单明细检查库存流水;最后给会员增加积分,并导出 Excel 报表。这趟流程覆盖了 90% 的功能,还特别能说明事务、库存、订单之间的联动关系。
答辩时少讲“我的系统用了什么技术”,多讲“我的核心业务流程是怎么设计的”。比如描述库存预警时,说清楚预警阈值在哪张表、查询时如何统计、列表如何标红,这比念 PPT 上的技术介绍强一百倍。
7.2 遇到不会的问题,我的兜底话术
老师常问的三件套是:登录校验怎么做、事务控制加在哪、数据库为什么这么设计。万一被问到没做过的点,比如“你们怎么防止并发超卖”,不要直接说“没做”。你可以说:我在扣减库存时使用了原子更新的 SQL 和受影响行数判断,这已经保证了数据一致性;在更极端的高并发场景下,还可以引入 Redis 预减库存或用数据库悲观锁,但毕设的重点是先保证核心逻辑可靠。这个回答既诚实,又展示了你的延伸思考。
7.3 我个人在整套实践里最大的收获
最后说点实在的。我带过的学生里,真正把 SSM 商店管理系统做完的人,往往并不是代码写得最花哨的,而是愿意按“需求—表结构—核心流程—页面—测试”这条线一步步走的人。一开始他们也会在分页插件和事务注解上卡很久,但这些问题恰恰是毕业后工作的常见问题。你只要把这一套逻辑吃透,之后学 Spring Boot 时你会发现,大量知识是平移的,只是配置方式变简单了。
所以我真心建议拿到这个题目的同学,不要急着把选题目录里的“源码+论文”当作终点。你要做的不是交一份作业,而是借这个机会完整地思考一遍一个真实商店的信息流如何运转。等你自己能讲清楚商品从入库、上架、下单到出库的全过程,这个毕设就已经值回票价了。