拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MySQL电商系统实战:库存扣减与订单状态设计

SpringBoot+Vue+MySQL电商系统实战:库存扣减与订单状态设计

1. 选题逻辑与整体架构:为什么用这套技术栈做雪具销售系统

做毕业设计这件事,最怕的不是代码写不出来,而是把题目想得太大,最后交付阶段的压力全堆在论文和答辩上。我选"雪具销售系统平台"这个题,核心原因只有一个:它把电商交易链路都覆盖了,又不至于复杂到要上消息队列和分布式事务。一套标准的B2C销售系统,商品、分类、购物车、订单、库存、会员、收货地址、后台统计,八个模块全部落在SpringBoot+Vue+MySQL这套经典组合里,工作量适中,亮点也够用——尺寸单板、滑雪服、护具、雪镜这些商品天然带多规格属性,方便在数据库设计里体现"分类关联+多图展示+库存变化"这些细节。

技术栈选定SpringBoot、Vue、MySQL,不是因为它新,而是因为这是面试官和答辩老师都最熟悉的一套组合。SpringBoot负责把后端的接口按RESTful风格暴露出去,Vue负责把前端页面拆成组件渲染数据,MySQL负责把商品和订单这些核心业务数据稳稳落盘。三个角色各管一摊,前后端通过JSON交互,这套逻辑讲起来非常顺,论文里"系统架构"那一章也最好写。

具体版本上我用的是SpringBoot 2.5.6、Vue 2.6.14、MySQL 8.0.27、MyBatis-Plus 3.4.2、Element-UI 2.15.6。可能有同学问为什么不用SpringBoot 3或者Vue 3,原因很实际:SpringBoot 3强制要求JDK17,很多学校的机房和服务器还停留在JDK8,Vue 3生态虽然成熟但配套的Element-Plus、Vue-Router、Vuex都要重新熟悉一遍,毕业设计求稳的前提下,版本不必追新,追新反而可能给自己埋坑。

整个项目我拆成了三个端:用户端(也就是商城前台)、管理端(后台管理页面)、后端服务。用户端跑在8080端口的SpringBoot应用上,提供RESTful API;管理端和用户端是同一个Vue工程里通过路由和权限控制的,不需要拆两个前端项目。这样做的理由是:毕业设计答辩时老师通常会让你现场演示,拆两个项目容易在切换时出幺蛾子,同一个工程里用router和角色字段控制页面访问,演示起来最顺手。

权限控制我用的是JWT(JSON Web Token)。用户登录成功后,后端生成一个token返回,前端存在localStorage里,之后每个请求都在Header里带上Authorization: Bearer xxx。后端用拦截器统一校验,放行登录注册接口,拦截其余接口,再根据用户角色字段判断是管理员还是普通会员。这套机制简单可靠,论文里也能画出完整的时序图。

2. 数据库设计:雪具库存、订单与会员体系的三张核心表

数据库设计是答辩时最容易挨问的部分,也是很多同学做项目时最随意的地方。我见过不少人直接把所有字段塞进一张大表,答辩时老师问一句"你这个订单和商品是怎么关联的"就答不上来。所以这块我花了整个项目三分之一的精力去设计,核心逻辑参照了标准电商系统的表结构,再针对雪具商品的特殊性做了几个调整。

2.1 核心表清单和关系

整个系统一共设计了8张表:sys_user(用户/会员表)、product_category(商品分类表)、product(商品表)、product_image(商品图片表)、cart(购物车表)、order_info(订单主表)、order_item(订单明细表)、address(收货地址表)。表数量不多不少,既能讲清楚关系,又不会让论文里的ER图看起来像蜘蛛网。

sys_user是最基础的一张表,字段包括id、username、password(存的是BCrypt加密后的密文)、real_name、phone、role(0表示管理员,1表示普通会员)、status(0启用,1禁用)、create_time。这里要特别说明的是:为什么我用role字段而不是单独建一张角色表。RBAC(基于角色的权限控制)模型在真实企业项目里往往会拆成用户表、角色表、权限表三张,但对于毕业设计这种体量的系统,用整数枚举区分管理员和普通会员已经足够,论文里也可以提一句"本项目采用简化的角色模型,后续可扩展为完整的RBAC",这反而是个加分项。

product表是雪具销售系统的核心业务表。字段设计上除了id、name、category_id、price、original_price、description、cover_image这些常规字段之外,我特别加了三个:

  • stock:库存数量,下单扣减就靠这个字段。
  • sales:销量统计,前台商品列表按销量排序时用。
  • status:上下架状态,0上架、1下架。注意这里的逻辑:商品不是删掉,而是下架,这样历史订单里的商品信息才能一直追溯到。

order_info和order_item是订单模块的两张表,也是整个系统逻辑最重的部分。order_info存订单主表信息,比如order_no(订单编号,用时间戳加随机数生成)、user_id(下单人)、total_amount(订单总金额)、status(0待付款、1待发货、2待收货、3已完成、4已取消)、receiver_name、receiver_phone、receiver_address、create_time、pay_time、delivery_time。

order_item存订单明细,包括order_id、product_id、product_name(冗余存储商品名)、product_image(冗余存储商品图)、price(下单时的商品单价)、quantity(购买数量)。这里有两个细节值得在论文里单独讲:第一,订单明细里的商品名称和价格为什么要冗余?因为商品可能改价、可能改名、可能下架,如果不冗余,以后查历史订单就会看到"当前价格"而不是"下单时价格",这在电商系统里是不允许的。第二,为什么一张订单对应多张明细表?这就是标准的一对多关系,一篇订单详情页能展示多个商品的快照信息。

2.2 库存扣减方案:乐观锁与事务控制

库存扣减是整个后端最需要解释清楚的点,也是答辩老师最爱追问的。我选择的方案是:下单时用MySQL的行级锁加事务控制,核心SQL长这样:

UPDATE product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity}

这行SQL的巧妙之处在于WHERE stock >= #{quantity}这个条件。先尝试扣减,如果受影响行数为0,说明库存不足,直接抛出"库存不足"异常,由SpringBoot的@Transactional注解触发事务回滚,保证不会出现超卖。这比"先查库存再扣库存"的方式安全得多,因为两个操作之间有并发窗口,多个请求同时查到库存充足,然后一起扣减,就会把库存扣成负数。使用这条带条件的UPDATE语句,数据库会在行级别加锁,同一时刻只有一个事务能修改这一行的stock字段。

代价是下单并发量特别高的时候,行锁会带来一定的等待,但毕业设计场景里并发量根本到不了那个级别,反而是这种简单直接的方式最容易讲清楚。论文里我画了一张下单流程图,从"前端提交订单"到"校验库存"到"创建订单记录"到"扣减库存"到"清空购物车",每一步用文字配合,答辩的时候照着讲就行。

2.3 订单状态流转的设计

订单状态我用了一个整数枚举字段,通过数值大小表示不同的生命周期。0到4这五个状态的流转关系是:待付款可以变成已取消(用户主动取消)或待发货(支付成功);待发货可以变成待收货(管理员发货);待收货可以变成已完成(用户确认收货)。这个状态机的设计要注意一点:前端不能让用户随意修改订单状态,所有状态变更必须走后端接口,由后端校验当前状态是否允许跳转到目标状态。我在后端写了一个状态校验方法,比如待付款状态下不允许直接变成已完成,否则整个订单逻辑就乱套了。

数据库层面的索引也值得一提。order_info表我建了order_no的唯一索引和user_id的普通索引,order_item表建了order_id的普通索引。理由很朴素:用户查询"我的订单"时走的是user_id,按订单号查询时走的是order_no,没有索引的话数据量一旦上百条,连本地查询都肉眼可见地变慢,答辩演示时不划算。

3. SpringBoot后端:从商品CRUD到订单状态机的一次成型

后端工程我用了标准的四层结构:Controller、Service、Mapper、Entity。有人可能觉得毕业设计不用搞这么"重",但我强烈建议你保留这个分层,因为答辩老师第一眼就看你的项目结构是否清晰,而且后期写论文时,"系统实现"一章可以直接照着Controller和Service的类名来组织内容。

3.1 统一返回结果与全局异常处理

前后端分离项目里,接口返回的数据格式必须统一。我自己定义了一个Result类,泛型设计,code表示状态码(200成功、500失败、401未登录)、message表示提示信息、data表示业务数据。所有接口都返回这个结构,前端拿到之后再根据code判断处理逻辑。这样做最大的好处是前端axios拦截器可以统一处理错误提示,不用每个页面都写一遍错误处理代码。

全局异常处理用的是SpringBoot自带的@RestControllerAdvice注解,配合@ExceptionHandler捕获自定义的业务异常。比如下单时库存不足,Service层抛出BusinessException("库存不足"),全局异常处理器捕获后统一返回Result.error("库存不足")。这样Controller层就不用每个方法都写try-catch,代码干净得多。这个设计在论文里非常好写,一张通配的异常处理逻辑图,再加一段代码展示,内容量就上去了。

3.2 商品模块:条件分页查询的通用写法

商品列表接口是整个用户端访问量最大的接口,我设计了一个支持多条件组合查询的分页接口:GET /api/product/list?pageNum=1&pageSize=10&categoryId=2&keyword=雪镜&sort=price。其中categoryId按分类过滤,keyword按商品名称模糊搜索,sort支持按价格、销量、上架时间排序。这个接口用的是MyBatis-Plus的Page对象加LambdaQueryWrapper来拼接查询条件:

LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>(); if (categoryId != null) { wrapper.eq(Product::getCategoryId, categoryId); } if (StringUtils.hasText(keyword)) { wrapper.like(Product::getName, keyword); } wrapper.eq(Product::getStatus, 0); // 只查询上架商品 Page<Product> page = productMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);

有同学会纠结要不要写复杂的XML动态SQL,我劝你别。MyBatis-Plus这套Wrapper写法已经足够覆盖毕业设计的所有查询场景,而且代码写在Service层里可读性更强。答辩时候老师看到Lambda表达式,至少知道你对Java 8的新特性有概念。

3.3 购物车模块:临时数据的合理处理

购物车表的设计比较轻量,id、user_id、product_id、quantity、create_time、update_time,加一个user_id + product_id的唯一索引。前端"加入购物车"接口的逻辑是:先查这个用户购物车里有没有这个商品,有就加数量,没有就新增一行。这里我不推荐用"删掉重新插"这种做法,因为会丢失加入购物车的时间,而且也没必要。

购物车列表接口返回的时候,我做了联查:通过product_id去product表查出商品当前价格、封面图、上下架状态。注意这里有个细节:购物车里保存的quantity是用户选择的件数,但商品当前价格要从product表实时取,不能用购物车表里的冗余价格。原因是商品可能改价,购物车页面应该展示最新价格,而不是用户加入时的价格。这个逻辑在论文的"系统实现"部分值得花一个小节去解释。

3.4 下单接口:事务墙内的三步操作

下单接口是后端的重头戏,也是毕业设计里含金量最高的一段代码逻辑。整个方法用@Transactional注解包起来,内部做四件事:

  1. 校验购物车选中的商品,逐条检查库存是否足够。
  2. 生成一条order_info记录,订单编号用System.currentTimeMillis()加上四位随机数生成,总金额遍历购物车商品累加。
  3. 逐条生成order_item明细,同时执行前面讲的库存扣减SQL。
  4. 清空购物车中对应的商品记录。

这里最容易被忽略的是:@Transactional默认只回滚RuntimeException,如果在业务逻辑里自己catch了异常不往外抛,事务是不会回滚的。我最开始写的时候在Service里用try-catch把异常吞掉了,结果测试时发现库存扣了但订单没建成,找了一晚上才意识到是事务没生效。后来我在Service方法上统一不写try-catch,让异常直接抛到全局异常处理器里,事务的回滚行为就正常了。这个坑我建议你实现的时候直接避开。

3.5 订单模块:状态流转与定时取消

订单模块的接口我划分得比较细:GET /api/order/myOrders(分页查询当前用户的订单)、GET /api/order/detail/{orderId}(订单详情)、POST /api/order/pay(模拟支付)、POST /api/order/cancel(取消订单)、POST /api/order/confirm(确认收货)、POST /api/order/delivery(管理员发货)。每个接口都在Service层先校验当前订单状态,再执行状态变更。

模拟支付我处理得很简单,前端调支付接口,后端把订单状态从0改成1,同时记录pay_time。没有接真实的支付宝或微信支付SDK,因为那需要企业资质和回调地址,毕业设计不现实。但我论文里写了一句"系统预留支付接口,后续可对接第三方支付平台",答辩时老师露出"我懂"的表情,就不会深挖了。

订单超时取消我没用SpringBoot自带的定时任务@Scheduled去扫表,而是简单地在用户查询订单时判断:如果订单状态是待付款且创建时间超过30分钟,自动修改为已取消。这个做法虽然不够严谨(用户不查询就不会触发状态变更),但对演示场景来说已经够用了。论文里可以提一句"这里可采用延迟队列优化,本系统为简化实现采用惰性检查策略",既显得你有思考,又不会给自己挖坑。

3.6 JWT登录与拦截器配置

认证这块我用的是jjwt库,登录成功后生成token,token里存userId和role两个字段。拦截器配置类继承WebMvcConfigurer,重写addInterceptors方法,注册自定义的JwtInterceptor:

registry.addInterceptor(jwtInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/register", "/api/product/**");

为什么商品查询接口要放行?因为前台商品浏览不需要登录,任何人都能看货架,下单才需要登录。这个设计非常贴近真实电商场景,答辩时值得讲一句"遵循了电商系统的公开资源与私密资源分离原则"。

服务端启动类记得加@MapperScan("com.shop.mapper")注解,不然MyBatis的Mapper接口扫描不到,启动就会报"Invalid bound statement"错误。这种问题在答辩演示前出现是灾难级的,因为我亲眼见过有人演示前半小时在加注解。

4. Vue前端:从商品浏览到下单结算的交互链路

Vue端我拆成了用户端和管理端两套视图,但共用同一个工程。用户端用Element-UI的卡片布局展示商品,管理端用表格加弹窗的方式管理商品和订单。路由设计上采用两级结构:/下是商城首页,/admin下是后台管理,通过路由守卫判断当前用户角色,如果是普通会员访问/admin就直接跳回首页。

4.1 商品列表页:筛选、排序与分页的联动

商品列表页左边是分类树,右边是商品卡片网格。分类树用el-tree组件渲染product_category表中的数据,点击某个分类时把categoryId传到后端,重新请求商品列表。顶部搜索框输入关键词后,调用商品列表接口并刷新表格。排序这块我用的是下拉框加sort参数,选"价格从低到高"就传sort=price&order=asc,选"销量优先"就传sort=sales&order=desc。

分页用el-pagination组件,currentPage和pageSize绑定到data里,切换页码或页大小时重新请求接口。这里有个页面交互上的小技巧:每次重新查询时都要把currentPage重置为1,否则用户在第三页筛选分类时,接口会带着第三页的页码去查只有几件商品的新分类,结果就是空白页。这个小问题新手很容易忽略,处理不好演示时看起来像Bug。

4.2 商品详情页与购物车联动

商品详情页展示商品大图、价格、库存、销量、详细描述,底部放一个"加入购物车"按钮和"立即购买"按钮。加入购物车成功后,右上角购物车图标上的badge数量要立刻更新。我实现的方式是:添加成功后调用一次查询购物车数量的接口,把返回值绑定到badge上。这个交互虽然简单,但整体体验会比刷新页面好很多。

购物车页面用el-table展示,每行有商品图、名称、单价、数量选择器(el-input-number组件,最小值1,最大值不能超过库存)、小计、删除按钮。底部是合计金额和"去结算"按钮。结算页面选择收货地址(地址列表来自address表)、确认商品清单,点击"提交订单"后调后端下单接口,成功后跳转到订单列表页。

4.3 订单列表:状态标签与操作按钮的动态渲染

订单列表页是用户端交互最复杂的一个页面,每个订单卡片展示订单编号、下单时间、订单状态、商品明细、总金额,以及一个跟状态绑定的操作按钮。这个按钮我用v-if做条件渲染:待付款显示"付款"和"取消"两个按钮,待发货显示"提醒发货"(实际不做事,只是按钮),待收货显示"确认收货",已完成为空。

这种设计背后的逻辑是:按钮必须跟订单状态严格对应,不能让用户在错误的状态下做操作。比如待付款订单如果显示了"确认收货",用户一点,后端校验不通过,弹出一个错误提示,这看起来就像系统Bug。状态和操作严格一一对应,演示的时候就非常流畅,点哪个按钮都是预期行为。

4.4 管理端:商品上架、订单发货与数据看板

管理端的商品管理页用el-table展示所有商品,提供"上架/下架"的开关切换、编辑弹窗(支持修改价格、库存、描述)、新增商品表单。新增商品时要上传封面图,我用的是Element-UI的el-upload组件,把图片传给后端的文件上传接口,返回一个图片URL存到cover_image字段。这里没有用对象存储OSS,而是直接把图片存到项目的upload目录,后端做一个静态资源映射,访问/images/xxx.jpg就能看到图片。这个方法在部署时有个需要注意的地方,后面部署章节我会细说。

订单管理页展示所有用户订单,按状态筛选。管理员看到待发货订单时,点"发货"按钮,填写物流单号(模拟,不做真实物流对接),订单状态从1变成2。数据看板页我用了ECharts画了两张图:一张是近7天销售趋势的折线图,一张是商品分类销量占比的饼图。数据接口是从order_item表和product表联查统计出来的。有实际图表的系统,在论文的"系统测试"章节里配图时会非常加分。

4.5 axios封装与跨域问题处理

前端请求我统一封装在utils/request.js里,基于axios创建实例,设置baseURL为/api,在请求拦截器里从localStorage取token并加到Header里,在响应拦截器里统一处理后端的Result结构:code为200时返回response.data.data,code为401时清除本地token并跳转登录页,其它code直接把message用Message.error弹出来。

本地联调时跨域问题怎么解决?我用的是SpringBoot的CORS配置。在后端写一个配置类实现WebMvcConfigurer,重写addCorsMappings方法,允许所有来源、所有接口跨域访问。开发环境下前端跑在8081端口(Vue默认),后端跑在8080端口,没有这个配置前端请求会被浏览器拦截。还有一个常见做法是Vue里配置proxy代理到8080,但我实际测试过CORS方式更省事,而且生产环境用Nginx反代根本不需要跨域,所以本地单纯为了联调方便选一种就行。

5. 论文与答辩:从代码反推论文结构的高效写法

很多同学把论文放在最后写,这是一个非常被动的策略。我的建议是:代码写到一半就开始搭论文框架,每完成一个模块就往论文里填一段。这样到后期,论文数据库设计那章可以精确引用真实的表结构,系统实现那章可以直接贴核心Service代码,测试那章引用的是真实跑过的测试用例。毕业论文是"先有系统后写论文",内容从上到下一气呵成,不用憋字。

5.1 论文目录与字数分配

我的论文是按这个目录组织的,可以参考:

章节内容字数占比
第一章绪论(背景、意义、国内外现状、研究内容)10%
第二章相关技术介绍(SpringBoot、Vue、MySQL、MyBatis-Plus)15%
第三章需求分析(功能性需求、非功能性需求、用例图)15%
第四章系统设计(架构设计、功能模块设计、数据库设计)20%
第五章系统实现(分模块展示核心代码和实现效果截图)25%
第六章系统测试(测试方法、测试用例、结果分析)10%
第七章总结与展望(工作总结、不足与改进方向)5%

第三章是需求分析,我写了管理员和普通用户两个角色的完整用例表。管理员的用例有:登录、商品分类管理、商品管理、订单管理、数据统计、会员管理;普通用户的用例有:注册、登录、浏览商品、搜索商品、加入购物车、管理购物车、下单、支付、确认收货、查看订单、管理收货地址。每个用例写清楚前置条件、基本流程、异常流程。这一章看着多,其实都是套模板的活,但它是论文里最能体现"需求分析能力"的部分,不能省略。

5.2 数据库设计章节的写法

第四章的数据库设计部分,我放了一张整体ER图,然后逐表列字段明细。字段明细表用三列表格:字段名、类型、说明。每张表还配了一段文字解释设计理由。比如product表为什么要冗余sales字段而不是实时从order_item表统计?因为商品列表页的排序和展示是高频操作,每次实时统计开销大,用冗余字段换取查询性能,代价是订单完成时要同步维护这个字段。这种"性能换一致性"的思考写出来,论文质量一下就上去了。

设计理由之外,我还写了表与表之间的关联关系说明:product.category_id关联product_category.id、order_item.product_id关联product.id、order_item.order_id关联order_info.id。这段写起来很快,但它是数据库设计章节的核心内容,不能只放图不写字。

5.3 测试章节的用例表套路

测试章节是最容易凑字数也最容易出彩的。我准备了两部分:功能测试和性能测试。功能测试直接用表格列出核心用例,比如"用户登录输入正确账号密码应登录成功""用户下单时库存不足应提示库存不足""未登录用户访问订单列表应跳转登录页"。每一条写清测试步骤、预期结果、实际结果、是否通过。性能测试我用JMeter对商品列表接口做了简单压测,100个线程循环10次,统计平均响应时间、错误率、吞吐量。这个数据是真实跑出来的,写在论文里非常有说服力,老师一看就知道你做过完整的系统测试而不是编造的。

5.4 答辩演练的侧重点

答辩PPT我准备了10页,结构是:选题背景、技术选型、需求分析、系统功能结构、数据库设计、核心功能展示、系统测试、总结展望。答辩时我会先花3分钟演示系统——打开用户端浏览商品、加入购物车、下单付款,再切到管理端发货、查看统计。演示完后老师一般会问技术实现,这时候重点讲三个点:库存扣减的SQL方案、JWT认证流程、订单状态机的设计。这三个点全部是从真实代码里提炼出来的,怎么问都答得上。还有一个容易被问的是"你这个系统有哪些不完善的地方"?我提前准备了答案:没有接入真实支付、图片存储方式简陋没有用OSS、没有Redis缓存和消息队列。每条后面都带一句"后续可以通过xxx改进"。

6. 从本机到服务器:JAR包部署与Nginx反向代理实操

部署环节大概是整套毕设里最让同学焦虑的部分,因为本机跑得好好的,一到服务器上就各种报错。我这次部署用的是CentOS 7.6的云服务器,2核4G配置,足够跑这套系统。整个部署过程我踩了不少坑,下面按完整流程讲一遍,你照着做基本能一遍过。

6.1 服务器环境准备

服务器上要装四样东西:JDK 1.8、MySQL 8.0、Nginx 1.20、Maven 3.6(只是为了打包,也可以本地打包好再上传JAR)。JDK用yum安装:

yum install -y java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64

MySQL 8.0的安装我推荐用MySQL官方提供的yum仓库方式,装好后执行systemctl start mysqld,再查临时密码:

grep 'temporary password' /var/log/mysqld.log

拿到临时密码登录后,第一件事是执行ALTER USER 'root'@'localhost' IDENTIFIED BY '你的密码';把密码改成自己的。这里有个坑:MySQL 8.0默认的密码策略要求密码必须包含大小写字母、数字和特殊字符,长度至少8位,如果你设置的密码太简单会直接报错。我当时就卡在这里,后来改成Root_123456这种带下划线和大小写的组合才通过。

Nginx用yum装也行,但CentOS 7自带的源里Nginx版本比较老。我直接从Nginx官网下载源码编译安装的,过程也不复杂,就三步:./configure、make && make install,装完后默认目录在/usr/local/nginx。

6.2 数据库导入与后端配置修改

服务器上的MySQL装好后,要把本地的数据库导出并导入到服务器上。本机用Navicat导出整个库的SQL文件,然后用命令行导入服务器:

mysql -uroot -p密码 < snow_shop.sql

导入之后,修改后端的application-prod.yml配置,重点改数据库地址和密码,还有文件上传路径。我把文件上传路径改成了Nginx的html目录下的upload文件夹,这样前端图片URL可以直接通过Nginx访问,不用再给SpringBoot单独配静态资源路径。

打包前还有一件事要注意:后端的pom.xml里如果配置了SpringBoot的maven-plugin,执行mvn clean package会打出一个可执行的Fat JAR。这个JAR上传到服务器后,直接就能启动。有的同学不小心打出的是普通JAR,启动时会报"no main manifest attribute",那种是没用SpringBoot插件打包的,检查一下pom.xml里有没有这个插件。

6.3 前端打包与Nginx配置

前端打包非常快,在Vue项目根目录执行:

npm run build

注意这里有个容易踩的坑:vue.config.js里要把publicPath设置为'./',不然打包后的资源路径是绝对路径/js/xxx.js,部署到Nginx的子目录时全都404。设置为相对路径后,静态资源会从当前目录找,就正常了。打包完成后,dist目录下就是纯静态文件,把它整个上传到Nginx的html目录:

cp -r dist/* /usr/local/nginx/html/

Nginx核心配置是反向代理,把前端的/api请求转发到后端的8080端口。在nginx.conf的server块里加这么一段:

location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

这样前端页面访问服务器的80端口,发起/api/user/login这类请求时,Nginx就把请求转发给后端的SpringBoot应用,整个过程对浏览器来说是无感知的,也不存在跨域问题。upload目录的静态资源则单独配一个location:

location /upload/ { alias /usr/local/nginx/html/upload/; }

6.4 后端Jar启动与进程守护

后端启动用nohup后台运行,日志重定向到文件:

nohup java -jar snow-shop-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &

--spring.profiles.active=prod指定生产环境配置文件。启动后先用tail -f app.log看启动日志,确认没有报错。看到"Started SnowShopApplication"字样就说明启动成功了,这时候在浏览器里访问服务器IP,就能看到商城首页。

进程守护这里我给一个小建议:不要裸跑java -jar,用nohup加&是目前最简单最不容易出错的方式。有些同学一上来就研究systemd服务脚本,不是不行,但一旦配置出错,排查起来很费时间,毕业设计阶段求稳优先。我踩过的另一个坑是:服务器重启后JAR进程没了,前端还能访问但所有接口都报错。解决办法是在/etc/rc.local文件里加一行启动命令,并给它执行权限,这样服务器重启后自动拉起后端进程。

6.5 端口开放与防火墙

部署完成后如果浏览器访问不了,99%是云服务器的安全组或防火墙没放行端口。我的服务器需要放行三个端口:80(Nginx默认端口)、8080(后端端口)、3306(MySQL端口,但强烈建议3306只对本地开放,不对公网开放,否则数据库裸奔很容易被黑)。云服务器安全组里配置好规则后,还要确认服务器本机防火墙状态:

systemctl status firewalld

如果防火墙开着,执行firewall-cmd --permanent --add-port=80/tcp和firewall-cmd --permanent --add-port=8080/tcp,然后firewall-cmd --reload重新加载。这个排查顺序一定不能乱:先浏览器访问看通不通,不通就curl服务器本机curl http://localhost,本机能通说明Nginx跑起来了,那就是安全组问题;本机都不通,先看Nginx启动状态和日志。

7. 复盘:做毕业设计时最容易白费的三个坑

整个项目从选题到部署上线,前后花了三周左右。回头看有几个坑差点让我前功尽弃,写在这里给准备做类似系统的同学提个醒。

第一个坑是数据库字段类型不一致导致的查询失败。我在设计order_info.status字段时用的是tinyint,但写MyBatis-Plus的查询条件时,在Java代码里把它当成字符串传了"0",导致SQL执行时把字符串跟整数比较,MySQL 8.0的严格模式直接报错,本地联调时一度以为MyBatis-Plus的eq方法写错了。排查了很久才意识到是类型不匹配。解决方案是在实体类里明确用Integer类型接收状态字段,前端传参时用Number而不是String。这个问题在答辩前发现是万幸,如果拖到演示现场,整个订单页面全挂。

第二个坑是前端联调时的跨域配置和生产环境配置混在了一起。我在request.js里直接写死了baseURL: 'http://localhost:8080/api',打包部署到服务器后就傻眼了,前端所有请求都指向本地8080,而服务器上根本没有浏览器能访问到那个地址。后来改成baseURL: '/api',本地联调时用Vue的devServer代理转发,生产环境由Nginx转发,同一套代码两个环境通用。当时改完就觉得,早期多花一点时间把环境配置做干净,后期部署是真的省心。

第三个坑是图片上传路径和打包JAR分离的问题。我把上传图片写进了项目静态目录,打包成JAR之后,上传的图片在JAR包外面,启动后又生成了一个新的路径,导致之前上传的商品图在切换启动方式后全部404。最后我把上传路径改成外部绝对路径,配置里写死为/usr/local/nginx/html/upload,JAR包和图片文件彻底分离,这个问题才算根治。这里也给后来者提个醒:SpringBoot的JAR包是封闭的,运行期写入的文件最好不要放在包内目录,否则容器重启文件就丢了。

从选题到现在,这套系统的代码量大概在八千行上下,不算多,但它覆盖了一个电商系统的核心链路。毕业设计的核心不是代码量堆得多高,而是你能否把"需求分析—数据库设计—接口开发—前端联调—部署上线"这条完整的链路讲清楚,每一个环节是否经得起老师追问。如果你正在做同类型的商城系统,希望这篇实践记录能让你少走一些弯路,哪怕省下一天的时间用来打磨论文,也值了。

返回列表