ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Spring Boot的网上购物系统毕设:核心模块与避坑实战指南

基于Spring Boot的网上购物系统毕设:核心模块与避坑实战指南 每年毕设开题季网上购物系统这类Spring Boot项目几乎是计算机专业学生最常碰到的题目之一。我当年写下基于Spring Boot的网上购物系统这个题名时也觉得这不就是照着一个普通商城抄一遍嘛但真正从需求分析做到答辩才发现这个题目的水比想象中深得多。这篇文章就把我当时从选题、数据库设计、核心模块实现到部署答辩的完整思路和踩坑过程展开聊聊。如果你正在准备的是Spring Boot毕设尤其是商城/购物类系统这篇可以直接拿来当行动清单。1. 网上购物系统的选题定调功能边界决定毕设评分1.1 先想清楚网上购物系统到底要做什么很多同学拿到这个题目的第一反应是打开某宝照着抄功能这其实是选题阶段最大的误区。毕设系统不是复刻完整电商平台而是用有限的时间证明你掌握了核心技术栈并把业务闭环打通。我建议把功能边界画成两条线一条是核心主线一条是加分支线。核心主线是所有购物系统绕不开的部分用户端注册登录、商品分类浏览、商品搜索、商品详情、购物车、下单结算、订单列表、确认收货管理端管理员登录、商品管理增删改查上下架、分类管理、订单管理发货/取消、用户管理加分支线则根据你的精力和答辩风格来定轮播图管理、评论模块、优惠券、模拟支付、数据统计图表、物流跟踪。说实话认真做完核心主线配合1到2个加分模块在答辩中已经能站稳脚跟。我见过不少同学堆了一堆半成品功能结果每个模块都有小bug答辩被问倒反而不如把核心功能做扎实。另一个容易忽略的是角色权限的区分。前台用户和管理员到底怎么共用一个登录入口我的做法是在用户表里加role字段普通用户是USER管理员是ADMIN前端根据登录接口返回的角色控制页面路由后端在需要管理的接口上加权限校验。这样既不用拆两套用户体系又能讲清楚权限控制这个技术点。1.2 技术栈选型为什么Spring Boot成了毕设默认答案网上购物系统的技术栈方案其实很多可以SSM、可以Spring Boot JSP、也可以前后端分离。我的推荐组合是Spring Boot 2.7.x MyBatis Plus MySQL 8 Redis Vue 3 Element Plus先说Spring Boot本身。它解决了SSM时代最大的痛点——繁琐的XML配置。Spring Boot通过自动装配把数据源、Web容器、事务管理器这些基础设施默认配置好让开发者把精力集中到业务代码上。毕设答辩被问到为什么用Spring Boot你要有个清晰回答它不是功能更强大的框架而是让Spring生态的集成成本大幅降低内嵌Tomcat让项目可以一键启动。MyBatis Plus在这个项目里帮了大忙。商品、购物车、订单这些都是典型单表CRUD如果用原生MyBatis写每张表都要写Mapper接口、XML、resultMap光重复代码就几千行。MyBatis Plus的BaseMapper自带selectById、insert、updateById这类方法单表操作基本不用写SQL。复杂查询比如订单分页、商品搜索再用LambdaQueryWrapper搞定。数据库我选MySQL 8这是目前高校机房和云服务器最常见的选择。如果你选了MySQL 5.7也不是不行只是记着驱动连接串里时区参数要配好后面我会讲。Redis在这个项目里的定位很明确做验证码存储、JWT令牌的续期和热门商品缓存。千万别为了显得技术含量高就硬塞Redis答辩时问你数据一致性反而容易给自己挖坑。前端技术选型上我建议有一定基础的同学直接走前后端分离Vue 3 Element Plus Axios Vite。原因很简单——分离架构让接口设计和跨域处理成为答辩时的技术亮点。如果你对前端不太熟用Spring Boot Thymeleaf这种模板引擎方案也能过关只是系统架构上少一个可以聊的点。2. 数据库设计是这个系统的地基别急着写代码2.1 核心表结构怎么设计最合理我先分享一个血泪教训数据库表设计不严谨后面每个模块都在给前期设计还债。购物系统核心表大概6张外加1张可选的地址表我列一下字段设计表名核心字段关键说明userid, username, password, phone, avatar, role, status密码不存明文用BCrypt加密role区分USER和ADMINcategoryid, name, sort, status商品分类sort控制前台显示顺序productid, name, photo, price, stock, sales, detail, status, category_idstatus控制上下架sales为虚拟销量字段cartid, user_id, product_id, quantity联合唯一索引(user_id, product_id)防止重复加购ordersid, order_no, user_id, total_price, status, receiver_name, receiver_phone, receiver_address, create_time订单号唯一status为订单状态order_itemid, order_id, product_id, product_name, product_photo, price, quantity冗余商品名称和图片防止商品删除后订单明细查不到数据库设计上有几个细节我在实际开发中反复踩到第一订单表不能只存用户ID一定要冗余一份收货人信息。你想用户改地址后历史订单如果关联地址表显示就会变而且地址删除后订单直接成了脏数据。直接把收货人姓名、电话、地址存进订单表才是电商系统的常规做法。第二order_item要冗余商品快照。商城里商品价格会变但订单里的价格应该维持下单那一刻的状态所以order_item里存了当时的价格、商品名和图片。这和业务逻辑有关答辩时主动讲出来是很加分的。第三订单号不要用数据库自增主键直接暴露给前端。我在项目里用的是时间戳 随机数生成的order_no这样任何人没法通过订单号推断平台的单量。2.2 订单状态机的定义让流程可控而不是一团乱麻订单状态是购物系统最核心的业务状态在我实际设计里是整型枚举五个状态走一个闭环待付款(0) → 待发货(1) → 待收货(2) → 已完成(3)另有已取消(4)作为分支状态这么设计的好处是清晰。待付款的订单如果用户取消状态变为4待发货可以由管理员发货后变成待收货用户点击确认收货后变为已完成。每次状态流转后端要做状态校验比如已取消订单就不能再发货。如果不在后端校验前端直接调接口就能把订单状态改乱。另外orders表要建create_time索引因为后台管理需要按时间倒序查订单。很多人在建表时忽略这个查询场景数据量一大后台订单列表就慢得让人抓心。地址表我建议如果做就认真做不做也可以。用下单时直接填写收货信息的方式一张订单表就全搞定了。我当时把地址表省了换来的是核心流程更聚焦答辩时也没有因为地址模块被追问。3. 核心功能模块的实现链路从登录到下单全流程3.1 登录认证用JWT比Session更合适前后端分离在这个项目里登录功能首先要回答的是认证机制选型问题。传统Session方案在前后端分离架构下比较尴尬跨域要配Cookie前端要维护Session ID后端重启后Session丢失。我直接用JWTJSON Web Token。JWT把用户ID和角色信息加密放进Token里后端无需存Session做到无状态认证。具体流程是用户传用户名密码 → 后端用BCrypt校验密码 → 生成JWT返回前端 → 前端每次请求在请求头带Authorization: Bearer token→ 后端用拦截器解析token获取当前用户。我实现JWT用的是io.jsonwebtoken包jjwt核心配置在application.ymljwt: secret: your-secret-key-change-in-production expire: 604800 # 7天有效期单位秒拦截器里我放行了登录、注册、商品浏览这些无需认证的接口订单相关接口统一校验权限。这里有个关键点要提醒拦截器注册要排除静态资源和前端页面否则登录页的图片样式全部加载不出来排查起来非常晕。密码存数据库之前一定要加密。我试过用MD5后来发现常规的MD5加不加盐都容易被彩虹表破解最后改用了BCryptPasswordEncoder。这个类在Spring Security里也可以单独引入spring-security-crypto依赖只加密不启用整个Security非常轻量。3.2 商品浏览与购物车细节全部藏在最简单的模块里商品列表接口本身不难难点在分页和条件查询的组合。用MyBatis Plus的LambdaQueryWrapper把分类ID、关键字、价格排序组合起来LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.isNotBlank(categoryId), Product::getCategoryId, categoryId) .like(StringUtils.isNotBlank(keyword), Product::getName, keyword) .eq(Product::getStatus, 1) .orderByDesc(Product::getSales); PageProduct page new Page(pageNum, pageSize); productMapper.selectPage(page, wrapper);这段代码很好的点在于eq和like前面的Boolean条件保证参数为空时不拼接该条件这样前端不用传一堆空参数后端也只有一条查询链路。购物车模块我采用了数据表存储而不是Redis存储。为什么因为购物车需要持久化用户换了设备购物车还在这是消费者视角的基本预期。用表存储的话就一行判断// 查询购物车是否已存在该商品 Cart existing cartMapper.selectOne( new LambdaQueryWrapperCart() .eq(Cart::getUserId, userId) .eq(Cart::getProductId, productId)); if (existing ! null) { // 存在则数量1 existing.setQuantity(existing.getQuantity() quantity); cartMapper.updateById(existing); } else { // 不存在则新增 cartMapper.insert(cart); }这个逻辑简单却是购物车模块的核心很多商城项目在这里没做合并同商品处理导致购物车里出现两行同款商品体验很差答辩演示时也很尴尬。3.3 下单事务与库存扣减把超卖问题讲明白下单购物车商品时我做的是一个事务性操作创建订单主记录、批量创建订单明细、扣减商品库存、清空购物车。这几个操作必须绑在同一个事务里否则可能出现订单创建了但库存没扣、或者库存扣了但订单失败了。Spring里最直接的写法是在Service方法上加上Transactional注解。但要注意事务不是加上了就一定生效后面避坑部分我会详细展开。库存扣减的超卖问题是购物系统答辩的高频考点。我当时用了MySQL的原子更新语句int updated productMapper.update( null, new LambdaUpdateWrapperProduct() .eq(Product::getId, productId) .ge(Product::getStock, quantity) // 库存必须够 .setSql(stock stock - quantity)); // 原子扣减 if (updated 0) { throw new RuntimeException(商品库存不足); }stock stock - quantity是数据库层面的原子操作再加上stock quantity条件两个线程同时下单同一商品时数据库锁会保证只有一个更新成功从源头上防止了超卖。这段代码我建议在答辩时主动解释属于你知道并发问题并采取过措施的加分项。支付模块我没有实际对接支付宝因为个人毕设申请商家接口比较麻烦。我实现的是模拟支付前端点去支付后后端做一次状态校验把订单从0待付款变到1待发货并记录支付时间。这个方案能满足毕设业务闭环但也建议你在论文里写明生产环境可对接支付宝沙箱或微信支付Native支付并简单提一下对接流程证明你了解实践方案。3.4 后台管理模块最容易被忽略却最影响答辩分数后台管理和前台业务比工作量不大但对答辩的影响非常直接——评委打开系统第一眼看的往往是你功能管理界面是否完整。后台核心我做了四块商品管理列表分页、新增编辑、图片上传、上下架。图片上传落地到本地磁盘数据库存访问路径分类管理分类的增删改删除分类前要检查该分类下是否还有商品订单管理订单列表按状态筛选、订单发货、查看订单详情数据统计用聚合SQL按日期统计销售金额和订单数配合ECharts展示折线图这里我想提醒一个简单但回报极高的点后台界面不要只做CRUD列表加上几个统计数字。我在首页放了今日订单数今日销售额总用户数总商品数四个卡片再画一个近七天的销售趋势折线图。这个功能只需要一句SQLSELECT DATE(create_time) AS day, SUM(total_price) AS amount FROM orders WHERE status IN (1, 2, 3) AND create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(create_time)就这么一个功能让系统在演示时立刻有了完整度和可视化的感觉。4. 开发阶段最容易翻车的四个环节我的排查笔记4.1 Spring Boot版本选择2.x还是3.x不是小事如果你是用Spring Initializr在IDEA里直接建项目默认很可能是Spring Boot 3.x。这个版本有几个对毕设来说很致命的改变JDK最低要求17并且原有的javax.servlet包全部替换成了jakarta.servlet。如果你查资料找到的大部分参考代码都是2023年以前的那么它们用的都是javax直接粘到Spring Boot 3项目里就是一片红色的import报错。我当时果断锁定了Spring Boot 2.7.18配合JDK 8或JDK 11这是目前网上购物系统毕设参考代码最丰富的组合。网上找的任何示例基本都能直接跑通。选版本不是越新越好生态兼容性和搜索资源的可得性在毕设场景里优先级更高。Spring Boot 2和3还有一个差异要注意就是Spring Security 5和6的配置写法差异很大。如果MyBatis Plus你用的是老版本在Spring Boot 3里也容易碰到starter兼容问题。一句话总结别在版本问题上追求尝鲜稳定复现比什么都重要。4.2 Transactional不生效这题在答辩现场出现概率极高我下单模块测试时遇到过一个问题模拟库存不足抛出异常后订单居然还是创建成功了。排查下来是Transactional失效这个问题我自己踩过几个常见的坑列出来供你自查第一个方法被同类内部调用。Spring的事务是通过AOP代理实现的同类里另一个方法直接this.orderCreate()调用走的是对象自己而不是Spring代理事务拦截器根本接不到。解决办法是拆到不同的Service里互相调用或者自己注入自己。第二个异常被try-catch吃掉了。Transactional默认只在RuntimeException和Error上回滚。如果你在事务方法里catch了异常没有重新抛出那事务判断一切正常只会正常提交。正确做法是catch后要throw new RuntimeException(xxx)或者用Transactional(rollbackFor Exception.class)来覆盖所有异常。第三个方法不是public。Spring的CGLIB代理无法拦截非public方法这个比较隐蔽因为代码不会报错只是静默失效。第四个数据库引擎不是InnoDB。MySQL的MyISAM引擎不支持事务如果你建表时用了默认之外的引擎事务怎么配都不会回滚。建议确认所有表的引擎是InnoDB。4.3 跨域配置与POST请求乱码前后端分离开发时前端Vite跑在5173端口后端跑在8080端口浏览器直接请求就会出现CORS跨域报错。我当时加了一个全局CorsFilterConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意allowedOriginPatterns(*)和allowCredentials(true)要同时使用因为带了凭证的跨域不能直接用allowedOrigins(*)。最开始我用了allowedOrigins(*)浏览器报了CORS request not http改掉就好。POST请求乱码这个坑更多出现在传递中文参数时。要确保前端Axios请求头是Content-Type: application/json;charsetUTF-8后端Spring Boot在application.yml里配一下编码server: servlet: encoding: charset: UTF-8 force: true4.4 图片上传后重启就丢了本地存储路径的一课商品图片上传我最初存在了项目里的src/main/resources/static/upload下。开发时一切正常但每次重启Spring Boot图片全不见了。原因很简单IDE在rebuild时会把target目录清掉重来而上传文件如果落在target/classes/static里自然没法存活。正确的做法是把上传文件保存在项目之外的固定磁盘目录同时用WebMvcConfigurer把URL路径映射过去Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file:D:/upload/); }这样前端访问/files/20250101.jpg实际上是读取D:/upload/20250101.jpg。数据库里也存/files/20250101.jpg这个路径。部署到服务器时把file:D:/upload/改成file:/usr/local/upload/就行。这个改动虽小但能避免一个看起来特别低级的演示事故——上传的新图片刷新就404。5. 打包部署与答辩准备做完项目只是完成了50%5.1 Maven打包和后端部署的完整流程Spring Boot项目打包部署说起来就是一条命令但里面的坑也不少。最标准的流程项目根目录执行mvn clean package -DskipTests在target目录下会生成一个可执行的jar包Spring Boot内置Tomcat所以是完整可运行的然后执行java -jar shopping-system.jar默认端口8080。如果你在本机测试注意8080端口是否被占用Windows下用netstat -ano | findstr 8080查看。如果部署到云服务器还需要在安全组放行8080端口不然外部一直连不上。对前后端分离项目前端构建更简单npm run build生成dist目录。你可以把整个dist复制到Spring Boot的src/main/resources/static下重新打包也可以单独用Nginx部署把后端接口反代到8080。毕设场景我推荐前者一个jar包全搞定演示时只需一键启动省去Node环境依赖。有个部署细节值得专门提一下application.yml里的数据库连接字符串要改成部署环境的配置。本地开发用的是localhost服务器上要改成云数据库的内网地址或公网地址账号密码也可能不同。很多同学项目本地跑得好好的一部署就报数据库连接失败大多是这里没改。5.2 演示数据准备和演示流程设计一个很实在的建议在答辩前准备好一套完整的演示数据。我准备了两个账户普通用户test、管理员admin、6个商品分布在3个分类下、每个分类都有商品在售、还有几条不同状态订单待付款、待发货、待收货、已完成各一条。演示时从注册登录开始走一遍检索商品、加购、下单、模拟支付、管理员发货、确认收货的完整链路。这套流程走完大概8分钟正好覆盖评委最关心的业务闭环。演示时务必先把MySQL、Redis、后端、前端全部启动好一遍确认无报错再开始录屏。我的习惯是本地录一遍完整流程存在手机里万一现场演示时网络卡顿或浏览器异常放录屏兜底。5.3 答辩追问环节这五个问题提前背熟答辩被问技术问题是有规律可循的。我把自己被问到的和同学被问过的整理成高频五问第一个Spring Boot自动装配原理。核心回答SpringBootApplication包含EnableAutoConfiguration它通过META-INF/spring.factories加载自动配置类再由ConditionalOnClass等条件注解按需生效。你说到条件注解这个层级基本就是满分答案。第二个MyBatis Plus和MyBatis的区别。站在项目角度回答MyBatis Plus内置通用Mapper和条件构造器单表CRUD零SQL但复杂多表查询仍然用原生的Select注解或XML编写避免滥用。这个回答体现出你了解工具边界。第三个购物车数据为什么放数据库而不是Redis。回答Redis读写快适合做缓存购物车放数据库保证持久性和跨端一致性。Redis在这里做验证码缓存、高频读取的热门商品缓存。第四个库存超卖如何解决。回答了上面update ... set stock stock - #{num} where id #{id} and stock #{num}的原子更新方案后可以再补一句如果在低并发场景已经够用高并发则可以升级为Redis Lua脚本预扣减或者引入分布式锁。这句话表示你理解更深一层的延伸。第五个订单状态为什么不用字符串而用int。回答状态是一个固定集合存储用int节约空间查询走索引更快语义用常量类统一管理避免字符串散落各处。项目里我用了OrderStatusEnum枚举来统一维护这层语义。这段内容写到这想说的是网上购物系统这个题目看起来很烂大街但它覆盖了Spring Boot生态中最主流的Web开发全流程从分层架构到事务、缓存、认证、文件上传、部署运维每个点都是毕设的核心得分点。希望这份从功能边界到数据库设计从核心实现到避坑记录的完整链路能让你在这个经典题目里做出自己的深度。踩过的坑我写在前面了你绕过去把时间花在把业务细节打磨完整上——答辩时你会感谢自己当时没在版本问题和不生效的事务上死磕太久。
返回列表