ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue在线宠物用品交易网站毕设全流程实战

SpringBoot+Vue在线宠物用品交易网站毕设全流程实战 SpringBootVue这个组合做在线宠物用品交易网站可以说是我近几年见过最适合当Java Web毕设的题目之一。原因很简单它的业务复杂度处在“够展示能力”和“不会把自己逼疯”的中间位置——商城类的用户端、管理端、购物车、订单流转、库存扣减这些模块都在能完整体现你对前后端分离架构、数据库设计、接口规范的理解同时又不至于像电商平台那样累死累活。这篇文章我就把自己带这个题目的完整经验拆开从数据库设计到后端核心逻辑从前端联调到部署避坑尽量写细你照着做就能把项目真正跑起来。1. 项目整体设计与需求拆分1.1 毕设选题的定位与功能范围很多同学拿到“在线宠物用品交易网站”这种题目第一反应是“这不就是一个商城吗有什么好做的”。说实话这种心态很危险。答辩的时候老师最烦听到的一句话就是“这个功能就是xx系统自带的”所以你要做的不是堆功能而是把一个商城最常见、最核心的业务做扎实同时让每个模块都能在答辩时讲出设计理由。这个题目的定位我的建议是做成一个“前后端分离的B2C轻商城”核心包含两大端用户端前台注册登录、轮播图展示、商品分类浏览、商品搜索、商品详情、加入购物车、购物车管理、下单结算、模拟支付、订单状态流转、个人中心、收货地址管理等。管理端后台后台登录、商品分类管理、商品上下架与库存管理、订单管理发货、查看详情、用户管理、轮播图配置等。这个范围对于一个毕业设计来说刚好合适。它不需要秒杀、不需要拼团、不需要积分体系那些属于“锦上添花”但答辩时极容易被追问的问题。相反把“用户下单 — 生成订单 — 扣减库存 — 支付 — 发货 — 确认收货”这整条链路走通并且数据库字段设计合理这才是评委最认可的“业务闭环”。1.2 技术栈选型的底层逻辑技术栈选SpringBoot Vue本身是一个非常稳的选择原因有三点第一SpringBoot是目前Java Web最主流的框架它“约定优于配置”的设计让项目更容易启动和维护答辩时老师对这套架构的接受度最高不容易被刁难。第二Vue作为渐进式前端框架生态成熟Element UI一拉管理端页面就能很快成型配合Vue Router做路由、Vuex或Pinia做状态管理前后端分离的完整度一下子就上来了。第三这套技术栈的资料极多你遇到任何问题基本都能在中文社区找到答案对于毕业生来说这就是最大的安全感。当然选型不是越新越好。比如数据库层面MySQL 5.7或8.0都行JDK用1.8最省心SpringBoot用2.x系列的稳定版本就行不要盲目上SpringBoot 3.x——它要求JDK 17很多云服务器和教程配置还跟不上毕设没必要给自己挖这个坑。2. 数据库设计与SQL脚本编写2.1 核心表结构设计思路数据库设计是整个项目的地基。我见过太多同学代码写得挺顺一到建表就随意来最后联调时各种缺字段、类型不匹配改起来异常痛苦。这套买卖网站的表结构我推荐下面这些核心表实际做的时候照着扩展就行表名作用关键字段说明user用户表id、username、password、nickname、avatar、phone、status状态1启用0禁用category商品分类表id、name、icon、sort排序值、statusproduct商品表id、category_id、name、cover封面图、images详情轮播图、detail富文本详情、price现价、original_price原价、stock库存、sales销量、status1上架0下架cart购物车表id、user_id、product_id、quantity、checked是否勾选address收货地址表id、user_id、name、phone、province、city、district、detail、is_defaultorders订单表id、order_no唯一订单号、user_id、total_amount、status0待付款/1待发货/2待收货/3已完成/4已取消、create_time、pay_time、deliver_time、receive_timeorder_item订单明细表id、order_id、product_id、product_name、product_cover、price、quantity、total_pricebanner轮播图表id、image、url、sort、status这里有两个表的设计是关键中的关键必须重点讲。第一个是订单表。订单的status字段是整个商城业务的核心状态机我的建议是用0~4的整数表示0待付款、1待发货、2待收货、3已完成、4已取消。状态流转逻辑就是“下单生成0支付后变1发货后变2确认收货变3超时或者取消变4”。千万不要用字符串存状态字段值一乱后面前台查询订单列表就只能靠一堆if else去救。第二个是order_item也就是订单明细表。很多同学会把商品信息直接放在订单表里下单时用一列data存快照这属于偷懒设计。订单明细表单独存在的理由是用户下单后商品可能改价、下架甚至删除订单里必须保存“下单那一刻”的商品快照否则你后期做报表、用户查历史订单全对不上。所以order_item里把商品名称、图片、单价都冗余存了一份这不是重复是业务需要。2.2 SQL脚本中的关键预设数据与初始化逻辑SQL脚本不光是建表还要解决“项目跑起来之后不是空数据”的问题。一份好的脚本必须包含三类内容一是建库建表语句。表结构字段定义清晰字符集统一用utf8mb4排序规则用utf8mb4_general_ci要不然商品名称里面放个“”表情就乱码给你看。另外所有金额字段建议用decimal(10,2)不要用float否则浮点精度问题会让你怀疑人生。二是初始化分类数据和商品演示数据。至少准备3-5个分类比如“狗粮”“猫粮”“玩具”“洗护用品”“营养保健品”每个分类下面放5~10个商品。商品数据最好真实一些价格、库存、封面图链接都填好这样前台页面一打开就直接能看到完整效果做演示或者答辩截图都会很加分。三是默认账号。一定要插入一个管理员账号和一个测试用户账号密码不用搞太复杂统一用MD5或BCrypt加密后的值存进去。让我非常明确地说一句密码字段绝对不允许明文明着存这是答辩时最容易踩的雷。哪怕业务再简单密码入库前也要加密这是Java Web基本功评委看到明文密码基本就断定你对安全一无所知。3. 后端SpringBoot核心实现3.1 项目分层与目录结构设计后端的代码结构强烈建议按“controller-service-mapper-entity”四层来分包这是Java Web项目最经典的分层方式可读性最强答辩时也最顺嘴。com.example.petmall ├── controller // 接口层对外暴露HTTP接口 ├── service // 业务层处理核心业务逻辑 ├── mapper // 数据访问层操作数据库配合MyBatis-Plus ├── entity // 实体类映射数据库表 ├── common // 通用类统一返回结果、状态码、异常处理 ├── config // 配置类拦截器、跨域、静态资源映射 └── util // 工具类JWT工具、密码加密等层与层之间严格单向依赖controller调serviceservice调mapper不许越层。有些同学图省事在controller里直接写一堆数据库操作答辩时被问“为什么service层是空的”只能尬在原地。分层不是代码洁癖是把你“懂架构”这件事写在脸上。MyBatis-Plus强烈建议直接用它比原生MyBatis的开发效率高太多。简单查询连SQL都不用手写一个selectById搞定复杂一点的用QueryWrapper或者LambdaQueryWrapper搞不定的再写自定义XML。对于毕业设计这个体量大部分接口都是CRUD级别用MyBatis-Plus能省出大量时间去打磨前端和文档。3.2 登录鉴权方案与拦截器设计登录鉴权这块我推荐用JWT理由很简单前后端分离的场景下JWT无状态、不需要在服务端维护session前端拿到token后每次请求放到请求头里后端通过拦截器统一解析非常契合这个架构。JWT的接入分三步走。第一步用户登录成功后后端生成一个token返回给前端。token里可以放userId和username再设置一个过期时间比如7天。密钥不要硬编码在业务代码里建议放配置文件中。// 生成token示例 String token Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(username, user.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000L)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();第二步添加拦截器。拦截器的作用范围是/api/**受保护接口登录接口和注册接口要放行。拦截器里主要做一件事校验请求头里有没有tokentoken是否合法、是否过期。校验失败就统一返回401前端拿到401就知道要跳登录页了。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { // 返回未登录的JSON响应 return false; } try { Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token.replace(Bearer , )); return true; } catch (Exception e) { // 返回token过期的JSON响应 return false; } } }第三步前端axios在请求拦截器里把token带上。// axios请求拦截器 instance.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config })3.3 核心业务流程实现要点这里必须重点说两个业务接口因为它们是整个项目的“题眼”也是答辩时一定会被追问的地方。下单接口。它的完整流程是这个样子前端提交收货地址、勾选的购物车商品 - 后端根据userId查出购物车里的商品 - 统一校验商品是否上架、库存是否足够 - 计算订单总金额 - 生成订单记录和订单明细记录 - 扣减商品库存 - 清空购物车中已下单的商品 - 返回订单编号给前端。这里最考验功力的地方是库存扣减。很多同学写的是“先查询库存if库存0就扣减”这在单用户时没问题但一旦并发下单就出问题了可能两个请求同时读到库存为1然后同时扣成0导致超卖。解决办法很简单扣减库存时在SQL里同时判断库存大于0用一条update语句完成UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity}注意stock #{quantity}这个条件必须写在SQL里而不是先查后改这样才能保证数据库层面的原子性。这一条如果你能在答辩时主动讲出来绝对是个加分亮点。购物车加购接口。这个接口的细节在于“同一个商品如果已经在购物车里应该数量加一而不是再插一条新记录”。很多同学的实现是用户每次点加购就insert一条最后购物车数据全是重复行。正确做法是先根据userId和productId查购物车存在就更新quantity不存在才新建记录。// 伪代码示意 Cart cart cartMapper.selectOne( new LambdaQueryWrapperCart() .eq(Cart::getUserId, userId) .eq(Cart::getProductId, productId)); if (cart ! null) { cart.setQuantity(cart.getQuantity() 1); cartMapper.updateById(cart); } else { Cart newCart new Cart(); newCart.setUserId(userId); newCart.setProductId(productId); newCart.setQuantity(1); cartMapper.insert(newCart); }后端接口统一返回格式也很重要。我建议整个项目的返回结构统一如下{ code: 200, message: 操作成功, data: { } }code用200/400/401/500这些语义化状态码不要自己发明一套“1成功2失败3未登录”之类的前后端对接时能省掉无数口舌。4. 前端Vue页面实现与前后端联调4.1 页面框架与路由设计前端用Vue 2 Element UI或者Vue 3 Element Plus看你自己版本路由设计直接决定用户体验和答辩观感。一个完整的宠物用品商城前端路由可以这样规划路由路径页面说明/Home.vue首页含轮播图、热门商品推荐/product/listProductList.vue商品列表页支持分类和关键字筛选/product/detail/:idProductDetail.vue商品详情页/cartCart.vue购物车页面/checkoutCheckout.vue下单结算页/order/listUserOrderList.vue用户订单列表/user/infoUserCenter.vue个人中心含地址管理/admin/loginAdminLogin.vue后台登录/adminAdminLayout.vue后台布局框架/admin/productAdminProduct.vue后台商品管理/admin/orderAdminOrder.vue后台订单管理/admin/categoryAdminCategory.vue后台分类管理/admin/userAdminUser.vue后台用户管理/admin/bannerAdminBanner.vue后台轮播图管理布局上建议这么拆分前台用一个主布局组件顶上放导航栏和搜索框下面放路由出口后台用侧边栏顶栏的经典布局。这里最忌把整个后台页面全部写在一个很大的LongPage.vue里面涉及到两三屏的代码后期改一个按钮样式都得滚动半天。路由守卫必须做。前台用户没登录就访问购物车或订单页直接redirect到登录页后台路由统一加上meta: { requiresAdmin: true }判断用户角色不是admin就踢走。这是前后端分离项目前端安全的第一道门槛做出来答辩时“安全性”就有得说。4.2 axios请求封装与跨域处理axios不封装就开始写请求的等于前面做了一桌好菜最后盐没放到位总差一口气。我的封装方案如下import axios from axios import { Message } from element-ui import router from ../router const instance axios.create({ baseURL: /api, // 开发环境走Vue代理 timeout: 10000 }) // 请求拦截器携带token instance.interceptors.request.use(config { const token localStorage.getItem(token) if (token) config.headers[Authorization] Bearer token return config }) // 响应拦截器统一处理错误 instance.interceptors.response.use( res { const r res.data if (r.code 401) { localStorage.removeItem(token) router.push(/login) } return r }, err { Message.error(服务器异常请稍后重试) return Promise.reject(err) } ) export default instance跨域问题如果用Vue开发服务器npm run dev最省事的方案是在vue.config.js里配置代理让前端请求走同源路径后端不用费劲去写CORS配置// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, // 后端端口 changeOrigin: true } } } }这里有个细节后端接口统一加/api前缀前端请求也统一发/api这样代理规则写起来非常舒适比那种一半接口带前缀一半不带再改来改去的体验好一百倍。4.3 购物车与订单流程的页面交互前端最难处理的不是数据展示而是购物车勾选状态与金额联动。当你勾选一个商品、取消勾选、修改数量时底部的“已选商品合计金额”要实时变化。建议直接用Vuex或者Pinia把购物车数据放在全局状态里管理页面组件通过计算属性getter去计算勾选总额而不是在每个组件内部零散地维护一堆data变量。// Vuex购物车模块简化示例 state: { cartList: [] // 购物车商品列表 }, getters: { checkedProducts(state) { return state.cartList.filter(item item.checked) }, checkedTotalAmount(state) { return state.cartList .filter(item item.checked) .reduce((sum, item) sum item.price * item.quantity, 0) } }下单页面的交互流程通常是这样购物车点击结算 - 跳转到订单确认页 - 展示收货地址列表默认选中一个 - 展示商品清单只显示勾选商品 - 点击“提交订单” - 后端返回订单编号 - 跳转到模拟支付页 - 点击“模拟支付成功” - 调支付接口 - 订单状态变待发货。这段流程里的细节是订单确认页不能再从购物车接口去读数据了要在跳转时把勾选商品的数据通过路由参数或状态管理带过去或者后端提供一个“根据购物车勾选条目计算结算清单”的接口。我的建议是后端写一个/cart/settlement接口返回所有勾选商品和总金额前端下单时再真正提交这样更符合前后端分离的思路。5. 部署上线与避坑经验实录5.1 本地启动环境准备与数据库导入账户密码我说了无数遍这里最后再重复一次数据库连接信息务必放在application.yml或application.properties配置文件中不要写死在代码里尤其不要放到entity包或者util包里成为一个常量类。配置文件应该是这个样子的server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pet_mall?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl数据库导入的操作用Navicat或者命令行都可以核心是先创建数据库再选择.navicat执行SQL脚本。如果你用MySQL 8.x连接时驱动必须用com.mysql.cj.jdbc.Driver老版本的com.mysql.jdbc.Driver会出现时区报错。另外数据库连接串一定要带serverTimezoneAsia/Shanghai否则大概率遇到Server returns invalid timezone错误。启动后端时先启动MySQL服务保证数据库端口3306能被本地访问再启动SpringBoot主类。如果项目里依赖了Redis或者RabbitMQ这些中间件开发环境能不用就不用——毕设的项目尽量保持“一个数据库一个后端一个前端”的最小启动链路不要给自己增加无意义的启动负担。5.2 高频报错排查我这个项目调试过程中遇到过不少坑大部分是典型的毕设问题单独列一个速查表你遇到时直接对号入座报错现象原因解决方案前端请求接口时404接口路径写错或代理没生效检查axios的baseURL与后端Controller的RequestMapping是否完全一致确认vue.config.js代理已配置前端请求接口返回500后端SQL错误或空指针看后端控制台异常堆栈MyBatis-Plus报错大多和表字段映射有关检查entity里的TableField注解启动时端口被占用8080已被另一个进程占用直接改server.port为8081或者用netstat -ano找到PID后结束进程中文乱码数据库字符集不是utf8mb4建库时设置字符集连接串加characterEncodingutf8mb4登录后刷新页面跳回登录页token没存在localStorage存在了sessionStorage确认前端存储统一用localStorage上传的图片访问不到后端没配置静态资源映射SpringBoot添加WebMvcConfigurer把本地磁盘访问路径映射到/images/**打包后Vue页面刷新404nginx没配置try_files路由回退在nginx的location里添加try_files $uri $uri/ /index.html;MyBatis-Plus分页不生效没有配置PaginationInnerInterceptor在config里添加MybatisPlusInterceptor插件注册PaginationInnerInterceptor接口能访问但前端拿不到数据报CORS开发环境代理没配置或生产环境跨域开发用代理生产用nginx反向代理不要依赖后端自己写跨域配置其中CORS跨域是想重点给各位加粗提醒的。以前端和后端分离开发时生产环境部署后不会再有“跨域”这一回事——因为nginx把前端和后端都配成同一个域名下的不同路径请求自然就是同源的。如果你非要通过后端写CrossOrigin或者全局CorsFilter来解决部署时反而容易踩坑。所以我的建议是本地开发不要打开后端的跨域配置直接用Vue代理生产环境用nginx一把梭。5.3 打包部署与答辩前的最后检查本地开发没问题以后打包部署是毕设的另一个大坑。前端执行npm run build后会把打包产物输出到dist目录。这里有两种常见部署思路一是把dist目录放到SpringBoot的static文件夹下打成jar包二是用nginx托管前端dist同时把后端接口反向代理到SpringBoot端口。给个nginx配置参考server { listen 88; server_name localhost; root /home/petmall/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里try_files $uri $uri/ /index.html;就是为了解决前端路由在刷新页面时出现404的问题——不配置这条用户刷新订单页直接就白屏了答辩现场碰到这种状况真的很惨。后端打成jar包用mvn clean package或者IDEA右侧Maven工具里的package就行产物在target目录下java -jar xxx.jar启动。第一次打jar包的同学容易漏配置数据库账号密码或者端口为默认记得提前检查配置文件里是不是写着正确的生产环境参数。答辩前最后两天的检查清单我按自己过去给学弟学妹做模拟答辩的经验列一份数据库脚本能不能一键完整导入导完是否有演示数据前端刷新页面会不会白屏对应路由回退配置未登录访问购物车/订单页会被踢回登录页吗下单时库存不足会有什么提示错误提示是不是“库存不足”这种正常文案用户下完单后台订单列表能不能看到并且能发货后台修改商品状态上架/下架后前台商品列表是否实时变化项目启动端口是否固定数据库连接是否写死在代码里接口文档是否和最终代码完全一致很多同学前期写完文档后来改了接口却没更新文档设计说明书里的数据表结构是否和SQL脚本一致第8条和第9条是特别容易被忽略的但到了评阅老师手里接口文档和数据库设计文档就是他们几分钟内判断你项目“真不真”的直接依据。接口文档里写的是/user/login代码里实际却是/users/login文档是答辩前的一个重要检查项。另外接口文档建议用专业格式来写。不用搞Swagger那么重按照常规接口文档模板每个接口包含接口地址、请求方式、请求参数、返回示例、状态码说明。比如登录接口文档可以这样### 用户登录 - 地址POST /user/login - 请求参数JSON - username用户名必填 - password密码必填MD5加密后传输 - 返回示例 { code: 200, message: 登录成功, data: { token: eyJhbGciOiJIUzI1NiJ9..., userInfo: { id: 1, username: admin, nickname: 管理员 } } } - 状态码 - 200登录成功 - 400账号或密码错误 - 500服务器异常接口文档不需要写得多花哨重点是一致性、准确性和可读性。你能把每一个接口的用途、参数、返回给描述清楚答辩时评委根据文档抽查两三个接口你都能对答如流论文的分就很稳了。项目做完之后我自己最深的感受是这个题目真正的价值不在于“会写代码”而在于你理解了一整套前后端交互的机制。数据库字段为什么要这样设计订单状态为什么用数字表示库存扣减为什么要放在SQL判断里axios为什么要统一封装当你把这些问题都能用自己的话讲清楚你就不只是在交一个毕设而是在展示一个初级Java开发人员的完整素养。按照这篇文章的思路把项目搭出来、跑起来、文档补齐你就已经跑赢了绝大部分同期毕设选手。
返回列表