首页 / 资讯中心 / 文章详情

SSM架构游戏商城系统:从Spring+MyBatis配置到答辩避坑实战

SSM架构游戏商城系统:从Spring+MyBatis配置到答辩避坑实战 ★ FEATURED ARTICLE
简介一套基于SSM框架SpringSpringMVCMyBatis实现的乐购游戏商城系统采用B/S架构并分为前台用户端与后台管理端适合Java方向毕业设计、课程设计以及需要快速搭建商城项目的开发者。源码经调试可直接在IDEA或Eclipse中运行配套MySQL数据库脚本同时提供答辩PPT和毕业论文文档解决从环境配置、前后台功能实现到论文撰写的一系列问题。后台涵盖管理员管理、游戏分类、商城游戏、订单信息、轮播图、留言板等模块前台支持注册登录、游戏浏览、收藏与个人中心功能完整可作为毕设参考或二次开发基础。资源包共797个文件总大小22.52MB。文件以111个Java源码、156个JavaScript和44个Vue前端组件为核心辅以163个SVG图标、46个CSS样式及图片素材另含2个SQL脚本和docx论文、PPT答辩文件目录结构清晰配合安装、启动、打包批处理脚本便于快速部署和按模块学习。已有103人学习下载适合需要系统掌握SSM开发流程并获取完整毕设素材的初学者。1. 为什么SSM架构的商城系统至今仍是Java课程设计与毕设的“安全牌”乐购游戏商城系统是一个典型的Java课程设计项目技术栈锁死在SSM框架Spring SpringMVC MyBatis前后台分离的B/S架构附带源码、数据库脚本、答辩PPT和毕业论文。这类项目在求职简历和毕业答辩里出镜率极高因为它在合适的复杂度上完整覆盖了Java Web开发的全部主链路从数据库建模、持久层SQL到控制器路由、业务事务再到前端页面和会话管理。虽然Spring Boot已经成了工业界主流但很多高校课程依然以SSM为教学骨架面试题里也反复追问Spring容器、MyBatis映射和事务传播。这篇笔记写给两类人一类是正在做Java课程设计或毕设、需要把这个项目从“会启动”做到“能答辩”的学生另一类是刚学完Java基础、想用一个完整前后台项目验证自己能力的开发者。我会从SSM三件套怎么分工讲起落到数据库表结构、前后台功能实现和5个高频踩坑点最后给出一份可照着操作的答辩验证清单和三个低成本加分改造。2. 拆解乐购商城的SSM三件套Spring容器、SpringMVC控制器、MyBatis映射怎么分工2.1 Spring管什么从数据源到事务边界的配置实例SSM里的Spring是容器也是整个项目的“对象管理中枢”。乐购商城里的UserController、OrderService、UserDao这些类全部交给Spring容器托管彼此通过依赖注入互相引用而不是在代码里new。理解这一点你就明白了为什么所有SSM项目都长着一张相似的面孔配置文件里先声明数据源再创建SqlSessionFactory然后让Spring去扫描Mapper接口最后加上事务管理器。我一般会把数据源放在spring-context.xml里管理用Druid连接池而不是Tomcat自带的DBCP原因有三个Druid自带监控页面答辩时能现场展示连接数和SQL执行统计它内置了连接泄漏检测省去很多排查时间配置参数比C3P0更直观。下面是一份可直接改用的applicationContext.xml核心配置context:property-placeholder locationclasspath:jdbc.properties/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ property nameinitialSize value5/ property namemaxActive value20/ property nameminIdle value5/ property namevalidationQuery valueSELECT 1/ property nametestWhileIdle valuetrue/ property nametimeBetweenEvictionRunsMillis value60000/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.legou.entity/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.legou.dao/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/这段配置里有三个点容易被忽略。第一typeAliasesPackage扫描的是实体包这样Mapper XML里写resultTypeProduct而不是全限定类名代码会清爽很多。第二MapperScannerConfigurer必须指定sqlSessionFactoryBeanName否则在多个数据源的场景下可能注入错误工厂。第三testWhileIdle配合timeBetweenEvictionRunsMillis之后空闲连接会被周期性的SELECT 1探活这能避免MySQL的wait_timeout把空闲连接断掉后Druid还傻乎乎地把坏连接发给业务层。事务边界我用的是Transactional注解方式而不是把所有事务配置堆在XML里。理由很实际Annotation方式能让事务边界紧贴业务代码答辩被问到“你这个下单方法的事务是怎么控制的”直接指到方法上的注解讲比在XML里翻几百行配置更有说服力。需要注意的是spring-context.xml里必须启动tx:annotation-driven否则所有注解全部失灵后面第4章会专门讲这个坑。2.2 SpringMVC的请求旅程前端控制器、HandlerMapping与JSON返回SpringMVC在这套架构里负责“接客”所有浏览器请求先打到DispatcherServlet它通过HandlerMapping找到对应的Controller方法执行完后把返回值交给视图解析器或消息转换器输出。乐购商场这类前后台项目页面请求走JSP或HTML渲染而前台商品列表、加入购物车、后台订单处理这些接口更倾向于直接返回JSON。两种返回方式在一套SpringMVC配置里共存靠的就是Controller配合视图解析器返回JSP以及ResponseBody把返回值写为JSON。下面是dispatcher-servlet.xml里最常用的一组配置mvc:annotation-driven/ mvc:resources mapping/static/** location/static// context:component-scan base-packagecom.legou.controller/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /beanmvc:annotation-driven/这一行是SpringMVC的启动钥匙它注册了RequestMappingHandlerAdapter、Jackson消息转换器等一系列必备组件。如果没有它ResponseBody返回的对象根本不会被转换成JSON页面只会显示一个奇怪的字符串或者直接报406错误。InternalResourceViewResolver里的前缀和后缀是约定Controller返回admin/product/list最终会去加载/WEB-INF/views/admin/product/list.jsp把JSP放在WEB-INF下还有一个附带好处浏览器不能直接访问WEB-INF里的文件必须经过Controller转发这在后台管理页面里天然多了一道保护。再看前端如何拿到商品数据。一个典型的JSON接口Controller长这样RestController RequestMapping(/api/product) public class ProductController { Autowired private ProductService productService; GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 12) Integer size, String keyword, Integer typeId) { PageHelper.startPage(page, size); ListProduct products productService.search(keyword, typeId); return Result.ok(new PageInfo(products)); } }RestController是Controller加ResponseBody的组合注解类里所有方法的返回值都会直接写成JSON响应体。RequestParam(defaultValue 1)把page参数的默认值收敛到后端前端不传页码时自动回退到第一页这个习惯能避免很多因为undefined传参导致的SQL异常。PageHelper.startPage(page, size)必须写在查询执行之前它通过MyBatis拦截器在运行时把limit参数拼进下一条SQL这条命令只对紧随其后的第一条查询生效所以分页查询方法里不能在这中间插入其他数据库操作。视图请求和JSON请求在同一个项目里共存这是SSM比纯Servlet开发顺手的地方。路由层面Controler方法返回String是跳转视图返回对象配合ResponseBody是输出JSON选哪种取决于页面形态商品列表页用AJAX拉JSON动态渲染后台表单提交后用redirect重定向回列表页避免刷新后重复提交表单。2.3 MyBatis的SQL边界动态SQL、一二级缓存与分页插件选型MyBatis在这套架构里是SQL的“定点爆破工具”。它不帮你生成全自动SQL而是让你把SQL写清楚、把参数映射做干净。乐购商城商品搜索是动态SQL最典型的应用场景用户可能只输入关键词、只勾选分类、或者只设置价格区间查询条件是可变的。用if标签拼接SQL比在Java代码里拼字符串安全得多也不会出现引号漏写导致SQL语法错误这种低级翻车。商品条件查询的Mapper XML片段select idsearch resultTypeProduct select id, name, type_id, price, stock, cover, status, create_time from product where if testkeyword ! null and keyword ! and name like concat(%, #{keyword}, %) /if if testtypeId ! null and type_id #{typeId} /if /where and status 1 order by create_time desc /selectwhere标签会自动去掉第一个and前缀这样每个if里都放心写and开头。使用concat(%, #{keyword}, %)而不是%${keyword}%是为了避免SQL注入。${}会被MyBatis直接拼接成SQL片段#{}编译成?占位符交给PreparedStatement处理。答辩时有老师会专门看这点一旦在模糊查询里用了${}印象分直接掉档。status 1用来过滤下架商品这个条件是固定写在外面的这样无论前面的条件怎么组合用户永远看不到后台已下架的商品。缓存方面MyBatis默认开启一级缓存也就是同一SqlSession内重复执行相同SQL会直接命中缓存不再打数据库。这在串联查询时能省不少开销但要注意一级缓存的生效范围极短SqlSession一旦关闭缓存就没了。二级缓存默认关闭我建议在课程设计里不要开。原因很直白二级缓存是跨SqlSession共享的如果你的Mapper里任何一个增删改操作恰好没有同步刷新缓存答辩演示时就会出现改了用户余额、列表还是旧数据的尴尬场面。SSM项目在并发量极低的场景下数据库自带的缓冲足够应付不需要为这点性能去买单。分页插件我选PageHelper而不是手写limit原因有三个手写limit必须每个查询接口都计算offset和pages代码重复度太高PageHelper自动执行count查询前端分页条的“共多少页/共多少条”直接能从PageInfo里取它支持多种数据库方言换库成本低。但PageHelper有一个不是玄学的坑startPage之后必须紧跟查询中间不能插入其他数据库操作否则分页参数会被错误的SQL消费掉导致某个不需要分页的查询被莫名加上limit。3. 前后台分离的设计与落地从数据库表结构到B/S架构下的会话管理3.1 数据库设计用户、商品、订单、购物车四张核心表的字段与关系乐购商城本质上是游戏虚拟商品的交易系统数据库设计围绕“用户—商品—购物车—订单”这条业务主线展开。B/S架构下所有数据都落在服务端MySQL里浏览器只负责展示和服务端返回的渲染结果这就要求表结构设计必须提前把前后台两种角色都用到的数据字段想清楚。一张合格的商品表至少要覆盖后台管理和前台展示两类需求后台关注库存和上下架状态前台关注名称、价格和封套图。下面是一份可以直接执行的建表SQLcreate table product ( id int primary key auto_increment, name varchar(100) not null comment 游戏商品名称, type_id int not null comment 分类ID1点卡 2道具 3账号 4周边, price decimal(10,2) not null default 0.00 comment 销售价, original_price decimal(10,2) default 0.00 comment 划线价, stock int not null default 0 comment 库存, cover varchar(255) default null comment 商品封面图, detail text comment 商品详情, status tinyint not null default 1 comment 1上架 0下架, create_time datetime default current_timestamp, update_time datetime default current_timestamp on update current_timestamp ) engineInnoDB default charsetutf8mb4; create table user ( id int primary key auto_increment, username varchar(50) not null unique, password varchar(64) not null comment 加盐后的密码, email varchar(100) default null, balance decimal(10,2) not null default 0.00, status tinyint not null default 1, create_time datetime default current_timestamp ) engineInnoDB default charsetutf8mb4; create table cart ( id int primary key auto_increment, user_id int not null, product_id int not null, quantity int not null default 1, checked tinyint not null default 1, create_time datetime default current_timestamp, unique key uk_user_product (user_id, product_id) ) engineInnoDB default charsetutf8mb4; create table order ( id bigint primary key auto_increment, order_no varchar(32) not null unique comment 业务订单号, user_id int not null, total_amount decimal(10,2) not null, status tinyint not null default 0 comment 0待支付 1已支付 2已发货 3已完成 4已取消, pay_time datetime default null, create_time datetime default current_timestamp ) engineInnoDB default charsetutf8mb4; create table order_item ( id bigint primary key auto_increment, order_id bigint not null, product_id int not null, product_name varchar(100) not null, price decimal(10,2) not null, quantity int not null ) engineInnoDB default charsetutf8mb4;这些表之间没有物理外键全部用逻辑关联。逻辑外键是课程设计项目的常见做法原因是分页、批量删除和订单历史归档时物理外键会拖慢写入性能删商品时也不希望因为外键约束报错挡住演示流程。product表里的type_id用int而不是varchar是为了后面扩展分类表时能直接join前台导航栏的“点卡、道具、账号、周边”四个分类都由这个字段驱动。order和order_item拆成两张表是订单系统的底线一张订单可能包含多个商品如果把商品信息直接塞进订单表统计“哪些商品卖得好”时就得用字符串拆分那是给自己挖坑。order_item冗余了product_name和price快照因为商品信息会变但订单里记录的价格必须是下单那一刻的价格。密码字段长度用varchar(64)是为了容纳SHA-256或BCrypt散列结果千万别设计成varchar(20)再拿明文直接存放。答辩时老师只要看到密码字段是明文数据库这块的分数基本就保不住了。Balance字段记录用户虚拟余额支付流程简化成余额扣减这在课程设计里完全够用不必引入真正的第三方支付接口。3.2 前台用户端商品检索、购物车与订单状态机的实现要点前台页面是真正使用B/S架构的部分浏览器向服务器发起请求JSP在服务端渲染完再返回给浏览器。商品列表页是用户进入乐购商城后的第一站常见做法是左侧分类导航加中间商品网格点击分类通过AJAX请求/api/product/list拉数据用JavaScript渲染卡片。这里有一个容易翻车的细节分页参数必须始终携带当前选中的分类否则翻到第二页时分类条件丢失页面会展示出所有分类的商品。购物车模块有两种实现路线存Session或存数据库。课程设计我有意推荐存数据库的cart表理由很实际Session购物车在浏览器关闭后数据全部丢失用户下次登录购物车空空如也这会让演示效果大打折扣数据库购物车可以把商品、数量、勾选状态全部持久化还能顺便和user表分页关联后台也能看到“哪个用户的购物车里有哪件商品”。代价是每次加入购物车要多一次数据库写入但在本地MySQL上这个开销完全感知不到。加入购物车和结算下单是事务的两个关键入口。购物车商品加入订单时需要保证“扣减库存”和“创建订单”同时成功或同时失败。前面已经配置了tx:annotation-driven所以核心方法只需要一个Transactional注解Transactional(rollbackFor Exception.class) public Order createOrder(CheckoutDTO dto) { Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(dto.getUserId()); order.setStatus(0); // 1. 创建订单主表 orderMapper.insert(order); // 2. 遍历购物车项校验库存并写入order_item for (CartItemVO item : dto.getItems()) { int rows productMapper.deductStock(item.getProductId(), item.getQuantity()); if (rows 0) { throw new BusinessException(商品库存不足: item.getProductName()); } OrderItem oi new OrderItem(); oi.setOrderId(order.getId()); oi.setProductId(item.getProductId()); oi.setProductName(item.getProductName()); oi.setPrice(item.getPrice()); oi.setQuantity(item.getQuantity()); orderItemMapper.insert(oi); } // 3. 清空已结算的购物车项 cartMapper.deleteByIds(dto.getCartItemIds()); return order; }这段逻辑的三个关键决策是deductStock使用带条件更新的SQLupdate product set stock stock - #{quantity} where id #{productId} and stock #{quantity}通过受影响行数判断库存是否充足而不是先查再改。先查再改在高并发下会超卖更重要的是代码多了一次查询、多了两个潜在的空值判断分支。rollbackFor Exception.class指定了回滚条件默认的Transactional只在RuntimeException时回滚如果业务层抛的是自定义BusinessException且没有继承RuntimeException事务就不会回滚这点我在第4章避坑里细讲。清空购物车放在最后一步前面任何一步抛错事务回滚后购物车数据原样保留不会出现“订单失败但购物车被清空”的反常识表现。订单状态用status整型字段驱动比用布尔字段存“是否支付、是否发货、是否完成”要可靠得多。0待支付、1已支付、2已发货、3已完成、4已取消这是一个单向不可逆的状态机取消操作只能发生在待支付状态发货只能发生在已支付状态完成只能由已发货状态推进。在Controller里每次状态流转都要校验当前status是否合法而不是直接set新值。后台管理端展示订单列表时同一行订单的状态也会随流转变化一条SQL的case when就能把状态码转成中文字段select id, order_no, user_id, total_amount, case status when 0 then 待支付 when 1 then 已支付 when 2 then 已发货 when 3 then 已完成 when 4 then 已取消 end as status_text from order order by create_time desc;3.3 后台管理端商品上下架、订单处理与权限拦截后台和前台共用一个数据库但后台的所有请求都必须经过登录校验。实现方式可以选SpringMVC拦截器也可以选Filter。我推荐HandlerInterceptor原因是它能拿到Controller的Handler信息可以做更细粒度的权限判断比如按请求URL区分“管理员可访问”和“运营人员可访问”。乐购商城的后台权限模型不需要太复杂一个统一的Admin后台登录即可。下面是一个后台登录拦截器的完整实现public class AdminInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object admin session.getAttribute(admin); if (admin null) { response.sendRedirect(request.getContextPath() /admin/login); return false; } return true; } }然后在dispatcher-servlet.xml里注册拦截规则mvc:interceptors mvc:interceptor mvc:mapping path/admin/**/ mvc:exclude-mapping path/admin/login/ mvc:exclude-mapping path/admin/css/**/ mvc:exclude-mapping path/admin/js/**/ /mvc:interceptor /mvc:interceptors/admin/**通配符拦截所有后台路径exclude-mapping放行登录页本身和后台页面的静态资源。如果忘了排除静态资源后台登录页的CSS和JS全部被拦截页面会以纯HTML裸样式加载答辩演示时非常难看。后台登录会话我存在HttpSession里没有用Spring Security或Shiro因为课程设计的核心是展示SSM框架本身的能力引入安全框架会让数据库表、配置文件和答辩问题都膨胀到超出必要范围。如果老师追问“权限怎么做的”拦截器方案能把从请求进入到放行的整个链路讲清楚。商品上下架在后台是一个按钮切换在数据库里就是update product set status ? where id ?。这里要注意下架不影响已存在订单的处理用户昨天买了一张点卡今天商品下架订单依然可以在后台发货。所以订单处理模块不能去校验商品的当前状态只需要关联商品快照信息。后台订单处理流程包括发货和取消发货需要校验订单处于待支付或已支付的状态取消则只能在待支付时执行。如果用户已经支付后台不能直接取消而要走“退款”的独立接口把订单状态置为已取消同时给用户余额回补。后台管理页面是一组JSP页面之间的跳转全部通过Controller和视图解析器完成。首页一般做一个简版数据看板显示今日订单数、今日销售额、商品总数、用户总数。这四类数据可以用四句select count或sum完成官方套路是写一个DashboardController一次性查询并填充到Model再交给JSP渲染。答辩时这页数据看板能直接展示项目的“完整业务闭环”比停在商品列表页要有说服力得多。4. SSM商城开发的5个高频踩坑点从环境配置到答辩前的性能检查这一章是我从自己和别人跑SSM项目时总结出的高频翻车记录。每一条我都按“现象、原因、解决”的顺序写方便你出问题时直接对着排查。环境差异会让问题表现各有不同但根源都集中在下面这些环节里。4.1 现象Tomcat启动报ClassNotFoundException现象是Tomcat启动到一半控制台抛出ClassNotFoundException: org.springframework.web.context.ContextLoaderListener或NoClassDefFoundError: com/mysql/cj/jdbc/Driver项目直接启动失败。原因通常是两个一是Maven依赖没有正确打到WEB-INF/lib目录本地开发时IDE里不报错但Tomcat加载的是部署后的war包二是Spring和MyBatis版本不兼容比如Spring 5搭配MyBatis 3.4以下的版本一些类路径已经变动。解决方法是先打开项目的Deployment Assembly或mvn package后的war包确认WEB-INF/lib里有没有spring-web、mybatis、mysql-connector-java这些jar。如果jar缺失回到pom.xml检查依赖的scope是否误写成了provided或test这两个作用域不会进入最终war包。版本兼容上我习惯直接用一套经过验证的组合Spring 5.1.x、MyBatis 3.5.x、MyBatis-Spring 2.0.x。这套组合网上资料最多遇到问题搜得到答案不必追新。4.2 现象AJAX请求返回500或JSON中文乱码现象是ResponseBody返回的JSON里中文变成???或者前端控制台直接报parse error另一种是请求过来返回500并在日志里看到No converter found for type。前者是SpringMVC默认的StringHttpMessageConverter使用ISO-8859-1编码写响应体后者是缺少Jackson依赖Spring无法把对象序列化成JSON。解决的组合拳是pom里显式引入jackson-databind依赖在dispatcher-servlet.xml里单独配置StringHttpMessageConverter并指定UTF-8。如果用了阿里Fastjson做消息转换器另外要注意Fastjson版本会存在安全通告课程设计里直接说“我用的Jackson”反而更能体现你对基础消息转换机制的理解。4.3 现象PageHelper分页查询数据不对现象是第二页数据重复、总页数比预期多一页、或者某条不需要分页的查询也被加了limit。原因几乎都出在PageHelper.startPage(page, size)的位置上。startPage用ThreadLocal记录分页参数只对紧接着的第一条查询生效如果中间隔着别的查询分页参数可能被那条无关SQL消费掉。另一种是高版本的PageHelper默认countnull也就是是否执行count查询取决于方言判断在某些连接池下可能不准。解决方法是把startPage紧贴目标select放中间不要有任何数据库查询同时显式声明PageHelper.startPage(page, size, true)强制执行count查询。还有一个琐碎但常见的点前端页码从1开始SQL的offset是(page-1)*sizePageHelper内部已经处理了这层换算但你自己的代码里如果又对page做了减一操作就会从第二页开始展示商品。4.4 现象Transactional事务不生效现象是方法里先扣库存成功、后写订单失败但异常抛出后库存依然被扣了没有回滚。原因有三种一是spring-context.xml里少了tx:annotation-driven等于没装事务开关二是注解写在非public方法上或方法内部自调用三是指定了rollbackFor但异常类型不匹配默认回滚只处理RuntimeException。解决方法是先确认spring-context.xml包含tx:annotation-driven transaction-managertransactionManager/且transactionManager指向的数据源和SqlSessionFactory用同一个DruidDataSource。然后检查注解位置Transactional只能放在public方法上同类内部调用this.createOrder()绕过了代理对象也不会走事务增强必须把自调用改成注入自己的Service或拆到另一个Bean里。业务异常统一继承RuntimeException并使用rollbackFor Exception.class兜底。4.5 现象答辩演示时数据库连接超时或页面卡死现象是上午演示时一切正常下午换到教室投影仪上首页打开转圈几十秒最后报Connection is not available, request timed out。原因基本是演示机器的MySQL服务没启动、数据库连接被mysql的wait_timeout回收、或者Druid连接池把空闲连接全部关了但服务端已经断开。解决方法是演示前固定做三件事确认MySQL服务是开机自启的在jdbc.properties的url上加上autoReconnecttrueuseSSLfalsecharacterEncodingutf8参数检查Druid配置里的validationQuery是否生效。最稳妥的做法是答辩前半小时打开系统把所有页面点一遍让连接池保持活跃。这个习惯能避免绝大多数演示翻车。注意所有密码、数据库账号参数不要硬编码在JSP里统一放jdbc.properties并用context:property-placeholder引入。答辩时老师会随手打开配置文件检查这点提前整理好能加分。5. 让这个项目从“能跑”到“能答辩”验证清单与三个低成本加分改造5.1 答辩前必过的功能验证清单按照下面这张表从前往后走一遍每一项都打勾达到这个状态基本可以保证演示全程不冷场。测试时清空生产数据、使用演示账号操作避免提审时出现脏数据。模块操作步骤预期效果注册登录注册新用户退出再登录密码显示为密文登录后跳到首页商品检索输入“点卡”关键字搜索只显示名称含“点卡”的上架商品分页正常购物车加入两件商品刷新页面商品仍在购物车中数量可增减下单结算提交订单模拟余额不足再提交一次余额不足提示明确余额充足时下单成功且库存减少后台登录未登录访问/admin/order被拦截器重定向到管理员登录页商品上下架下架一件商品回前台搜索前台搜不到该商品后台列表仍可见订单发货对已支付订单执行发货订单状态从“已支付”变为“已发货”5.2 三个可以低成本拿下的加分改造第一个改造是为下单接口增加参数校验在CheckoutDTO的字段上加NotNull和Min(1)注解Controller参数加Validated。这不但能前置拦截非法请求还能在答辩时展示“我考虑了接口健壮性”这个层面。第二个改造是给商品列表加一个简单的销量排序。只需要在product表加sales_count字段下单事务里对命中商品执行update product set sales_count sales_count ?列表查询的order by改成sales_count desc。改动量不超过20行代码但整个商城的“热销”概念落地了前台展示效果立刻变得真实。第三个改造是做一个三天的订单趋势图。Controller查出一条Mapdate, amount前端用ECharts读JSON画线一行AJAX加二十行JavaScript。答辩时这张图是全场视觉亮点它把数据库聚合查询、JSON接口、前端渲染完整串成了一条线。做这个项目最大的教训是我早期总是先改代码后想参数导致事务和分页的边界错乱最后花一整晚排查。现在我会先画一张请求时序图标出哪些方法必须开事务、哪些接口的参数可以复用DTO再动手写代码。希望帮到你也祝你的乐购商城项目能顺利通过答辩、写进简历里成为一份拿得出手的Java作品。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站