SSM超市管理系统实战:框架整合配置与避坑指南

发布时间:2026/10/13 21:35:52
SSM超市管理系统实战:框架整合配置与避坑指南 简介这是一份基于SSMSpring Spring MVC MyBatis框架的超市管理系统课程设计资源面向计算机专业进行Java Web课程设计、毕业设计或框架入门学习的读者。系统围绕商品管理、库存管理、销售管理、会员管理及统计报表等核心模块展开后端采用SSM三层架构前端包含JSP页面及HTML/CSS/JavaScript静态资源结构完整适合中初级开发者对照学习SSM整合与业务实现。压缩包共792个文件容量1.78MB主要包含Java源码与class字节码、JSP视图、XML配置文件、数据库SQL脚本以及大量HTML页面和GIF截图便于直接查看界面效果和运行逻辑。资源梳理了用户、商品、订单、供应商、菜单等实体的Controller与Mapper类目录层次清晰可帮助读者快速定位关键代码。目前已有84人学习下载适合作为课程设计报告撰写和答辩演示的参考素材。1. SSM超市管理系统为什么有人两天跑通有人卡了两周SSMSpring SpringMVC MyBatis超市管理系统是课程设计里出现频率极高的题目但同样是这套题交付质量的差距能拉开到几个量级。有人一周内跑通并写完了文档有人直到答辩前一天还在跟 404 页面搏斗。区别不在业务复杂度——超市系统无非是商品、库存、订单、供应商那点事——而在于你对三个框架协作边界的理解。Spring 管对象生命周期SpringMVC 管请求进出MyBatis 管 SQL 与结果集映射单独看文档都明白真正卡人的是它们之间的缝隙配置扫描范围、事务代理机制、静态资源放行、日期序列化规则。这篇文章的目标很具体让正在做这个课程设计的人从建工程到跑通业务再到答辩前查漏补缺每一步都有可照做的方案。内容顺序也是常规项目的推进顺序先把 SSM 的骨架和配置文件立住再设计数据库表和 Mapper 映射然后实现分页、登录、JSON 接口三个 Web 层重头戏最后把最容易踩的坑集中排掉。你不需要提前精通 SSH 或 SSM照着配置走即可但读完至少要能说清楚为什么这样配。2. 工程骨架与配置文件先把SSM三件套的分工立住2.1 版本选型课程设计场景选哪组依赖最稳SSM 的版本组合很现实地影响着你的排错效率。对课程设计来说我通常建议不要追新选资料密度最高的一套JDK 8 Maven 3.6 以上 Spring 5.0.x MyBatis 3.4.x mybatis-spring 2.0.x PageHelper 5.1.xMySQL 用 5.7连接池用 Druid 1.1.x。这套组合在各类技术社区里积累了海量的报错案例和博客模板你遇到的异常基本都能搜索到现成答案。有人会问为什么不用 Spring Boot毕竟 Spring Boot 确实能简化配置。但课程设计的评分表里常常明确写着体现 SSM 框架整合能力而且 Spring Boot 的自动配置会把很多关键细节隐藏掉你回答问题的时候说不清楚 DispatcherServlet 是怎么注册的、SqlSessionFactory 是怎么构建的反而减分。所以我的态度是能用 SSM 原始配置就老老实实用这是练习框架整合能力的好机会。注意别去碰 Spring 6 / MyBatis 3.5 以上 JDK 17 的组合。新版框架要求 jakarta 命名空间很多课程设计用的老模板和参考代码直接跑不了。我不否认新版本在性能上有优势但对这个场景稳定可复现才是第一优先级。2.2 Maven 工程结构与 web.xml入口配置是第一步我一般先建一个标准 Maven war 工程目录结构如下supermarket/ ├── pom.xml ├── src/main/java │ └── com/supermarket/ │ ├── controller/ │ ├── service/ │ ├── dao/ │ ├── entity/ │ └── interceptor/ ├── src/main/resources │ ├── spring/ │ │ ├── applicationContext.xml │ │ └── spring-mvc.xml │ ├── mapper/ │ │ ├── ProductMapper.xml │ │ ├── OrderMapper.xml │ │ └── ... │ ├── mybatis-config.xml │ └── jdbc.properties └── src/main/webapp ├── WEB-INF/web.xml ├── static/ │ ├── css/ │ ├── js/ │ └── images/ └── views/代码分层按 controller、service、dao、entity 四层拆后面写业务时不会乱。resources 下把 Spring 配置拆成 applicationContext.xml 和 spring-mvc.xml 两份目的是隔离根容器和Web 容器这是 SSM 整合的基本盘。pom.xml 里的核心依赖要精简别把用不到的包都拉进来。参考片段properties spring.version5.0.15.RELEASE/spring.version mybatis.version3.4.6/mybatis.version /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.6/version /dependency /dependenciesspring-jdbc 不是额外的事务管理器 DataSourceTransactionManager 在它里面没有它就配不了声明式事务。spring-webmvc 会自动传递依赖 spring-web 和 spring-context但 spring-jdbc 需要显式声明这是经常漏掉的一处。接着是 web.xml这是 SSM 的入口。这里有两个关键角色ContextLoaderListener 负责加载根容器DispatcherServlet 负责加载 SpringMVC 容器。context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping如果你的项目里只有一个 spring-mvc.xml也可以不配 init-param让 DispatcherServlet 默认去 /WEB-INF/dispatcher-servlet.xml 找。但课程设计里我建议显式指定 classpath 位置这样两个配置文件的职责一目了然答辩时讲起来也顺。提示url-pattern 用 / 表示拦截所有请求这也是后面静态资源 404 和 JSON 乱码问题的根源。不要因为暂时没遇到就跳过理解它。2.3 applicationContext.xml 与 spring-mvc.xml扫描边界必须划清继续配置 applicationContext.xml 时扫描逻辑要小心。根容器管 service、dao 和实体controller 交给 SpringMVC 容器。这是常见的设计约定目的是防止同一个 bean 在两个容器里各生成一份导致事务失效。一个清爽的写法如下。先看 applicationContext.xmlcontext:component-scan base-packagecom.supermarket context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/ context:exclude-filter typeannotation expressionorg.springframework.web.bind.annotation.RestController/ /context:component-scan再看 spring-mvc.xmlcontext:component-scan base-packagecom.supermarket.controller use-default-filtersfalse context:include-filter typeannotation expressionorg.springframework.stereotype.Controller/ context:include-filter typeannotation expressionorg.springframework.web.bind.annotation.RestController/ /context:component-scan根容器扫描 com.supermarket 全局再排除 Controller子容器只扫 controller 包并只接收 Controller 注解。这样 service、dao 只会被根容器管理一次SpringMVC 容器只管分发请求的 Controller。说完扫描顺便把 mybatis-config.xml 和 mapper 配置补上。mybatis-config.xml 里建议只放全局设置比如下划线转驼峰和 PageHelper 插件configuration settings setting namemapUnderscoreToCamelCase valuetrue/ /settings plugins plugin interceptorcom.github.pagehelper.PageInterceptor property namehelperDialect valuemysql/ /plugin /plugins /configurationmapUnderscoreToCamelCase 设为 true 后数据库字段 order_no 可以自动映射到实体属性 orderNo省去大量 resultMap 编写。这个是课程设计里性价比最高的配置项谁用谁知道。2.4 数据源与事务管理器声明式事务的最后一块拼图数据源、SqlSessionFactory、Mapper 扫描、事务管理器这四样通常在 applicationContext.xml 里统一配置。我是这么写的bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/supermarket?useUnicodetrueamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword valueroot/ property nameinitialSize value5/ property namemaxActive value20/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nameconfigLocation valueclasspath:mybatis-config.xml/ property nametypeAliasesPackage valuecom.supermarket.entity/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.supermarket.dao/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/mapperLocations 指定 XML 文件路径typeAliasesPackage 让实体类在 Mapper XML 里可以直接写 Product 而不用写全限定名。MapperScannerConfigurer 则自动为 dao 包下每个接口生成代理实现不需要自己写实现类。事务管理器绑定同一个 dataSource再通过 tx:annotation-driven 开启 Transactional 注解驱动。这一条配置的失败率极高后面第 5 章会细说。现在你只需要记住一个原则SqlSessionFactory、MapperScannerConfigurer、DataSourceTransactionManager 这三个 Bean 的 dataSource 必须是同一个对象这里漏了任何一个引用不一致都会出现连接断开或事务不生效的诡异问题。3. 数据库设计与MyBatis映射商品、库存、订单六张核心表先立住3.1 六张核心表字段、索引与关系设计超市系统的业务可以浓缩为三个动作进货、销售、统计。围绕它我一般会建六张表商品表 product、商品分类表 category、供应商表 supplier、用户表 user、订单表 orders、订单明细表 order_item。这样的粒度刚好够演示关联查询和事务也不会复杂到课程设计写不完。表结构参考如下表名关键字段说明productid, name, category_id, supplier_id, barcode, price, stock, status, create_time商品主表category_id 和 supplier_id 分别关联分类与供应商categoryid, name商品分类建议预置“饮料、零食、日用品”等数据supplierid, name, contact, phone供应商进货单时选择userid, username, password, role员工账号role 区分管理员与收银员ordersid, order_no, user_id, total_amount, status, create_time销售订单主表order_no 唯一order_itemid, order_id, product_id, price, quantity订单明细冗余商品单价字段类型上价格用 decimal(10,2) 而不是 float避免精度问题库存用 intorder_no 用 varchar(32) 存业务单号create_time 用 datetime。主键全部是 bigint 自增但订单表里不要依赖主键对外暴露业务上只用 order_no。外键这一项我建议不建物理外键只保留逻辑关联。原因很实际课程设计的删除操作通常要求分类下没商品才能删分类这类校验用代码控制比数据库级联更直观也方便你在项目文档里多写一段业务逻辑。但查询必须要走关联所以 category_id、supplier_id 这两个字段必须建普通索引ALTER TABLE product ADD INDEX idx_category (category_id); ALTER TABLE product ADD INDEX idx_supplier (supplier_id); ALTER TABLE orders ADD INDEX idx_order_no (order_no);索引不要加太多课程设计的数据量远远到不了需要调优的程度但字段上没索引会导致 MySQL 全表扫描哪怕数据量小执行计划解释起来也不好看。三四个索引足够。3.2 MyBatis 映射文件复用生成器还是手写 SQL很多同学喜欢用 MyBatis Generator 一键生成实体和 Mapper XML。我的建议是生成器可以出实体类和 DAO 接口但 Mapper XML 里的业务 SQL 不要全靠它。生成器生成的 CRUD 是固定套路一旦你后面加个关联查询、条件拼 SQL它帮不上忙而且重新生成时容易覆盖手写代码。我更推荐手写 Mapper XML或者只把生成器当起点。理由是我需要每个方法的 SQL 都烂熟于心答辩时老师随便指一个方法问你这条 SQL 为什么这样写你不至于对着生成代码说不出所以然。下面这个商品分页查询是最典型的写法直接用动态 SQL 拼多条件select idselectProductPage resultTypeProduct SELECT p.id, p.name, p.price, p.stock, p.status, c.name AS categoryName, s.name AS supplierName FROM product p LEFT JOIN category c ON p.category_id c.id LEFT JOIN supplier s ON p.supplier_id s.id where if testname ! null and name ! AND p.name LIKE CONCAT(%, #{name}, %) /if if testcategoryId ! null AND p.category_id #{categoryId} /if if teststatus ! null AND p.status #{status} /if /where ORDER BY p.id DESC /select标签会自动处理掉第一个条件前面的 AND这是 MyBatis 动态 SQL 里最实用的容错机制。参数占位必须用 #{}它底层走 PreparedStatement 参数绑定既防 SQL 注入又帮你处理字符串引号${} 是直接拼接 SQL只有在排序字段等动态列名场景才考虑用而且绝不能接收用户直接输入。课程设计里经常有同学图省事在 like 查询里写 ${name}这就是给答辩埋雷。3.3 订单号生成与库存扣减事务边界必须落在 Service订单业务是超市系统的核心也是事务最好的演示场景。一个创建订单的流程包含三步插入订单主表、逐条插入订单明细、扣减商品库存。三步要么全成功要么全回滚这就是 Transactional 存在的意义。我习惯把事务边界放在 Service 接口实现类的 public 方法上Override Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateDTO dto) { String orderNo generateOrderNo(); Order order new Order(); order.setOrderNo(orderNo); order.setUserId(dto.getUserId()); order.setTotalAmount(calcTotalAmount(dto.getItems())); order.setStatus(0); orderMapper.insert(order); for (OrderItemDTO item : dto.getItems()) { int rows productMapper.decreaseStock(item.getProductId(), item.getQuantity()); if (rows 0) { throw new RuntimeException(商品库存不足或商品不存在); } OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(item.getProductId()); orderItem.setPrice(item.getPrice()); orderItem.setQuantity(item.getQuantity()); orderItemMapper.insert(orderItem); } return order.getId(); }generateOrderNo 我建议用yyyyMMddHHmmss 4 位随机数的组合单机并发下通常够用。decreaseStock 对应的 SQL 必须写成原子扣减这个细节太重要单独放到第 5 章讲你先把扣减失败要抛异常触发回滚这个思路记住。rollbackFor Exception.class 一定要写。不写的话Spring 默认只对 RuntimeException 回滚检查异常默认不回滚。课程设计里最容易出现的场景是业务校验失败时抛了一个自定义 checked exception结果数据照写不误这就是没配 rollbackFor 的经典翻车。事务加在 Controller 方法上是无效的Spring AOP 代理默认不拦截 Controller最稳妥的做法就是放在 Service 实现类方法上并且通过外部调用进入而不是同类里 this.createOrder 这种自调用。4. 从 DAO 到 Controller分页查询、登录拦截与 JSON 日期模型4.1 商品分页查询集成 PageHelper 并保持查询条件干净超市管理系统的商品列表不可能一页展示完所以分页是刚需。实现分页有两种思路手写 limit 参数或者接入 PageHelper 插件。课程设计里我强烈建议用 PageHelper理由不是它省事而是它让你能把精力放在查询条件组织上而不是处理第几页、每页几条、总记录数这些重复代码。PageHelper 的用法非常固定在 MyBatis 查询前调用 PageHelper.startPage紧接着执行查询然后用 PageInfo 包装结果。public PageResultProductVO pageProduct(int pageNum, int pageSize, String name, Long categoryId) { PageHelper.startPage(pageNum, pageSize); ListProductVO list productDao.selectProductPage(name, categoryId); PageInfoProductVO pageInfo new PageInfo(list); PageResultProductVO result new PageResult(); result.setTotal(pageInfo.getTotal()); result.setList(pageInfo.getList()); return result; }Controller 层接收参数后直接调用返回统一结果体GetMapping(/product/list) ResponseBody public ResultPageResultProductVO list( RequestParam(defaultValue 1) int pageNum, RequestParam(defaultValue 10) int pageSize, RequestParam(required false) String name, RequestParam(required false) Long categoryId) { PageResultProductVO data productService.pageProduct(pageNum, pageSize, name, categoryId); return Result.ok(data); }PageHelper 的关键约束是 startPage 后面必须紧跟第一次真正执行的 MyBatis 查询中间不能夹带其他查询语句或业务处理否则分页会拦截到错误的 SQL。另外一个常见错误是在循环里反复调用 startPage这会导致分页状态串到下一次查询上。如果代码逻辑必须分支我一般会在分支内部各自执行 startPage 和查询而不是在方法开头统一 startPage。注意PageHelper 5.x 版本里 startPage 只对其后的第一条 select 语句生效。如果你在前后之间插入了一条 update 或另一条无关查询分页可能失效这是排查分页异常时最先要看的位置。4.2 登录拦截器区分未登录用户和管理员会话超市系统要区分管理员和收银员权限最常规的做法是基于 Session 的登录拦截而不是上 Spring Security。课程设计用 Security 太重了配置繁琐且不好讲一个 HandlerInterceptor 足以把需求说清楚。拦截器实现参考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; } }在 spring-mvc.xml 中注册时要放行登录相关的路径和静态资源mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/doLogin/ mvc:exclude-mapping path/static/**/ mvc:exclude-mapping path/css/**/ mvc:exclude-mapping path/js/**/ mvc:exclude-mapping path/images/**/ /mvc:interceptor /mvc:interceptors拦截器不要拦截 /login 和 /doLogin不然用户永远进不了系统。这个配置必须放在 spring-mvc.xml 里因为它是 SpringMVC 容器管理的组件放 applicationContext.xml 里 DispatcherServlet 不认。如果需要区分管理员与收银员可以在登录时把 role 写入 SessionpreHandle 里再校验一次前缀路径。例如 /admin/ 开头的请求要求 role 为 1否则返回 403。4.3 统一返回体与 JSON 日期序列化前端拿到的数据格式要稳定Controller 的返回值如果直接返回裸的 List 或 Map前端处理起来很混乱。我的习惯是包一层 Result 统一返回体包含 code、msg、data 三个字段成功时 code 为 200失败时返回业务错误码和提示信息。这样前端只需要解析一个固定结构遇到登录超时也能统一跳转。统一返回体的代码很简单public class ResultT { private int code; private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.msg success; r.data data; return r; } public static T ResultT error(int code, String msg) { ResultT r new Result(); r.code code; r.msg msg; return r; } // getter/setter 省略 }JSON 序列化由 Jackson 自动完成但日期字段默认输出成时间戳也就是一串毫秒数前端拿到根本没法直接展示。解决思路两个要么在实体日期字段上写 JsonFormat要么全局配置 ObjectMapper。课程设计我更推荐用注解直观且不需要额外配置JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private Date createTime;timezone 必须写成 GMT8否则在东八区环境下 Jackson 可能按默认时区输出时间差 8 个小时查单的时候对不上这是很常见的诡异 bug。如果你担心每个日期字段都加注解太啰嗦就去定义全局 Jackson 配置但注解方案在答辩时更容易当场说明白所以我默认用它。5. 避坑手册SSM 超市系统最常见的 5 个翻车点与排查路径5.1 静态资源全部 404DispatcherServlet 把 CSS/JS 拦走了现象登录页面样式全没了浏览器控制台一堆 Failed to load resource: the server responded with a status of 404css、js、图片全拿不到。原因web.xml 里 DispatcherServlet 的 url-pattern 配成了 /它拦截了所有请求也包括静态资源。SpringMVC 容器里找不到处理 .css 的 Handler直接返回 404。解决在 spring-mvc.xml 里配置资源映射mvc:resources mapping/static/** location/static// mvc:default-servlet-handler/mvc:resources 把 /static/** 路径映射到 webapp/static 目录default-servlet-handler 让无法处理的请求回退到容器默认 Servlet。这一步配置完CSS 和 JS 就能正常加载了。排查时先确认你的页面引用路径是 /static/css/style.css 还是 /css/style.css两套映射规则不一样路径对不上也会 404。5.2 事务悄悄失效Service 方法异常了数据还在写现象createOrder 里明明在库存扣减不足时抛了 RuntimeException但订单主表数据还是插入成功了库存也没扣。原因三个常见诱因。第一Transactional 写在了 Controller 方法上而 Controller 默认不参与事务代理。第二事务注解在私有方法上代理无法拦截 private 方法。第三同一个 Service 内部方法自调用比如 createOrder 里调用 this.updateStock()这属于 this 调用绕过了代理对象事务注解变成装饰品。解决把 Transactional 放在 Service 实现类的 public 方法上确保方法修饰符为 public并且通过外部调用链路进入。同一类内部不要自调用如果一定要抽公共逻辑放到另一个 Service 里注入使用。排查事务问题时可以在日志里打印当前连接是否开启了事务或者观察异常回滚后数据库数据是否残留。先检查方法修饰符再检查调用链最后检查配置里的 tx:annotation-driven 是否真的在根容器生效。5.3 MyBatis 报 Invalid bound statementMapper XML 压根没被加载现象项目启动没问题Controller 调 service 时抛org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.supermarket.dao.ProductDao.selectProductPage。原因Mapper 接口存在但对应的 XML 没有注册到 SqlSessionFactory。常见情况是 mapperLocations 配成了classpath:mapper/*.xml但 XML 文件放在了 src/main/java 目录下Maven 默认不把 java 目录下的 xml 打进 classes运行时加载不到。解决最干脆的做法是把 mapper XML 统一放到 src/main/resources/mapper 目录下然后确认 mapperLocations 路径一致。如果你的项目必须把 XML 放 java 包里在 pom.xml 加 build 资源配置但我不推荐因为这对课程设计来说毫无必要只会徒增困扰。另外检查 XML 文件里的 namespace 是不是接口全限定名。namespace 写成 com.supermarket.dao.ProductDao接口才能和 XML 对应上。这里报错的排查路径很固定一看 target/classes 下有没有 XML二看 namespace 和方法 id 是否匹配。5.4 商品库存超卖前端限购挡不住并发现象两个人同时下单商品库存只剩 10 件结果两个订单都成功库存变成 9 或者变成负值。原因典型的 check-then-act 竞态。很多实现是先 SELECT 库存判断库存大于下单数量再 UPDATE 扣减。两条线程同时读到库存 10都判断可以下单都去 UPDATE最后只扣了一次甚至不扣。解决把判断和扣减合并成一条原子 SQLupdate iddecreaseStock UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity} /update这条 SQL 让数据库行锁保证原子性只有在当前库存大于等于购买数量时才扣减影响行数为 0 表示库存不足。Service 里通过 int rows 判断失败并抛异常触发事务回滚订单就不会生成。这是超市系统里最值得强调的一个设计答辩时老师也大概率会往并发方向追问你能答出原子更新 受影响行数判断这一层就算稳了。5.5 日期字段变成一串数字Jackson 时间戳问题现象前端在订单列表里看到 createTime 显示为 1690000000000 这样的毫秒时间戳没法直接阅读。原因SpringMVC 默认用的 Jackson 会把 java.util.Date 序列化成时间戳。这是 Jackson 的默认行为不是 bug但不满足业务展示需求。解决三个办法选一个。字段上加 JsonFormat(patternyyyy-MM-dd HH:mm:ss, timezoneGMT8)或者在 applicationContext 里全局配置 ObjectMapperobjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)同时设置日期格式。如果你用了 fastjson那要换一套注解。课程设计里我不建议混用 JSON 库SSM 默认带的是 Jackson就一路用 Jackson 到底别为了省事引入其他库增加排错变量。6. 从「能跑」到「能答辩」验证清单、两个加分项和一个习惯系统跑通只是开始答辩才是终点。我的习惯是动态维护一份验证清单上线答辩前按清单逐项过一遍不凭感觉。清单不长但覆盖核心链路登录登出是否正常拦截未登录访问商品增删改查是否完整价格和库存字段有没有更新到位进货流程是否让库存增加并写入进货记录前台收银创建订单后库存是否准确扣减订单总额是否计算正确分页在第二页、空数据、搜索关键字无结果三种情况下是否正常部门权限隔离收银员有没有办法访问管理员接口。把这些步骤截图整理到项目报告里比贴十页代码有用得多。第一个加分项是登录操作日志。用一个拦截器或者 AOP 切面在 Controller 方法执行前记录操作人、操作时间、请求路径、参数摘要。课程设计只要把这个功能做出来老师会认为你考虑了系统的可审计性这是真实项目里很重要的非功能需求。实现上不用复杂一个自定义注解加一个 Spring AOP 切面即可十行代码的复杂度就能讲出一个漂亮的设计点。第二个加分项是收银台页面的简单统计卡片。用 ECharts 画一个当日销售额折线或分类占比饼图数据来源是订单明细表的聚合查询。这条不加也不影响功能完整性但加了之后整个系统的完整度会明显高一个档次而且统计查询正好覆盖了 MySQL 的 GROUP BY 和聚合函数让数据库设计部分更有说服力。最后一个习惯每写完一个接口就在项目笔记里记一行这个接口是给哪个页面用的、参数有哪些、返回结构是什么。我当年做这类课程设计时吃过亏写的时候很顺三天后再看自己代码要想半天。有了运行笔记整理报告和准备答辩会快很多也减少答辩时被问到细节却答不上来的尴尬。希望这个方法能帮你在 SSM 超市管理系统上少走弯路把精力留给真正该思考的业务逻辑和后端设计。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询