
做JavaWeb方向的人十有八九会在项目清单里遇见“花店销售系统”这类题目。我最近刚完成一套基于Spring Boot Vue的花店销售系统编号047从需求分析到数据库设计再到前后端联调部署完整走了一遍。这套系统不算复杂但麻雀虽小五脏俱全前台商品展示、购物车、下单流程、后台商品管理、订单处理、会员和统计报表都有特别适合拿来当毕业设计、课程设计或者作为Spring Boot Vue前后端分离的入门练手项目。很多朋友纠结这种项目要不要做前后端分离、要不要引入Redis、订单接口怎么处理库存我在这篇文章里会把这些关键问题一个个拆开讲顺便把开发过程中踩过的坑也一并记录。希望看完之后你不仅能照着把系统搭起来还能明白每一步为什么这么做。1. 项目概览花店销售系统到底解决什么问题1.1 传统花店管理的四大痛点实体花店的日常经营远没有想象中那么简单。大部分小规模花店还在用纸质记事本或者Excel管理订单每天打烊后再把当天的订单誊一遍经常出现漏单、记错价格的情况。尤其是节日期间玫瑰、百合、康乃馨的订单量突然上冲手写记录很容易乱客户催单时店员翻半天也找不到订单状态体验非常差。库存问题更明显。花材是鲜活商品进货多少、卖出多少、损耗多少纯靠老板的经验估算。一束花里面可能要搭配五六种花材每一种的库存都不一样人工记账很难做到实时准确。再加上鲜花有生命周期放几天卖不出去就要处理掉如果库存数据失真损耗统计更加无从谈起。客户关系也没有沉淀。谁经常买花、谁的生日快到了、哪位客户偏好什么风格这些信息都停留在店员的脑袋里。员工一离职客户资源就直接流失。对于一家想长期经营的花店来说这是非常可惜的事情。还有一个隐藏痛点数据统计全靠拍脑袋。本月营业额到底涨了多少哪种花最好卖哪个时段订单最多没有真实数据支撑进货和促销就只能凭感觉。花店销售系统就是把订单、库存、客户和统计数据全部在线化让老板打开后台就能看到实时经营状况。这个系统解决的不是单点问题而是把经营流程整个盘活。1.2 系统角色划分与核心业务流程这套系统按使用对象分成三类管理员、店员和普通顾客。管理员负责商品上下架、分类维护、订单审核、会员管理以及查看统计报表拥有系统最高权限。店员主要处理日常订单状态流转比如确认付款、备货、标记自提或者配送。普通顾客通过前端页面浏览花束和绿植把商品加入购物车提交订单后走模拟支付流程。核心业务流程可以概括成一条闭环管理员先维护商品分类再上架花卉商品并设置库存顾客在前端浏览商品将喜欢的商品加入购物车然后提交订单并模拟支付后台收到新订单后店员或管理员更新订单状态从“待支付”推进到“已支付”“备货中”“已发货”或“已完成”同时系统自动扣减对应花材的库存并在订单完成后把销售额写入统计数据。从这个流程能看出系统虽然以“花店销售”命名但本质上是一个完整的小型电商系统。商品、购物车、订单、支付模拟、库存、后台管理、统计报表每一个模块都是电商项目的核心零件。把这个项目做完再去做其他垂直电商系统基本就是换一层业务皮肤而已。2. 技术选型为什么是Spring Boot Vue2.1 从JavaWeb到Spring Boot现代JavaWeb的正确姿势标题里带有“JavaWeb”不少人会下意识想到Servlet、JSP和Tomcat那一套。实际上Spring Boot就是JavaWeb技术演进到今天最主流的工程形态。它没有抛弃Servlet规范反而把Servlet容器内嵌到应用里让你不需要单独部署Tomcat就能通过一个jar包启动Web服务。从本质上看Spring Boot仍然运行在JavaWeb的整套底层机制之上只是把繁琐的配置做了深度封装。Spring Boot最核心的能力是自动装配。启动类上的SpringBootApplication注解其实组合了SpringBootConfiguration、EnableAutoConfiguration和ComponentScan。当我们引入spring-boot-starter-web这个依赖时Spring Boot会根据classpath下已有的类自动帮我们创建Spring MVC相关的Bean不再需要写一堆XML配置。这就是为什么Spring Boot框架能大大降低JavaWeb项目的上手门槛。选择Spring Boot还有一个很实际的原因企业里中小型管理系统的技术栈几乎都是Spring Boot全家桶。目录结构清晰、社区资料丰富、生态完善遇到问题基本都能搜到答案。对毕设和实训来说Spring Boot比纯Servlet项目更贴近真实开发环境也比SSM手写配置的方案更省心。它不炫技但足够稳妥。2.2 Vue让前端从模板地狱里解放出来如果后端选JSP页面通常会写成混着Java代码和HTML标签的模板改一个样式可能连带触发后端逻辑变更前后端耦合得非常难受。Vue出现之后前端开发变成了独立的工程页面结构、样式、交互逻辑全部由Vue组件完成后端只需要对外提供JSON数据接口即可。Vue的核心优势是组件化和响应式。页面可以拆成商品卡片、分页条、购物车列表这些独立组件哪里需要就引用哪里复杂度被切成一个个小块。响应式数据绑定又让表单和列表联动变得非常自然比如在购物车页面修改数量总价会自动刷新不需要像老式jQuery那样手动操作DOM。更重要的是Vue的生态非常适合后台管理系统。配合Element Plus组件库表格、表单、弹窗、日历拖拖拽拽就能搭出很规范的界面。Vue Router负责页面跳转和路由守卫Pinia负责全局状态管理Axios负责HTTP请求这一套组合在业内已经形成事实标准。对开发花店系统这种管理型应用来说效率非常高。2.3 辅助工具与依赖的选型逻辑后端持久层我用了MyBatis-Plus。它本质上是对MyBatis的增强单表CRUD不需要手写SQL通过BaseMapper接口直接提供selectById、insert、updateById这些方法开发速度提升非常明显。复杂查询比如订单统计、商品分类分组依然可以使用注解SQL或XML灵活性没有被牺牲。登录鉴权我选择了JWT而不是Session。前后端分离之后后端接口是无状态的JWT把用户信息加密生成一串Token交给前端保存每次请求放在HTTP Header里传给后端。这样后端不需要维护Session映射水平扩展也更方便。拦截器配合JWT做统一验证代码结构很清楚。工具类方面Hutool是非常好用的Java工具库提供了日期处理、随机数生成、文件操作等大量封装。订单号生成我直接用Hutool的IdUtil.getSnowflakeNextIdStr()省掉了自己写雪花算法的麻烦。不过要注意工具类只是辅助核心业务逻辑还是要自己认真写不要为了省事把事务和并发控制也交给工具。3. 系统架构与工程结构规划3.1 前后端分离的请求如何流转很多初学者能跑通前后端分离的Demo却说不清一次请求到底经历了什么。拿“用户查看商品列表”这个场景来拆解用户在前端点击菜单Vue Router把页面路由到商品列表组件组件加载时调用封装好的api/getFlowerList方法这个方法内部通过Axios发起一个HTTP GET请求携带Token到Spring Boot的Controller。Controller接收到请求后先经过JWT拦截器校验Token是否有效如果有效就放行到对应的FlowerController。Controller不做业务计算直接调用FlowerService的查询方法Service再调用FlowerMapper执行SQL最终从MySQL里取回数据。返回之前把数据包装成统一的Result对象序列化成JSON传给前端。前端拿到JSON后更新响应式数据Vue自动渲染页面。这个流程里最关键的设计是Controller只做参数接收和结果返回Service只做业务处理Mapper只做数据库操作。如果Controller里写了一大堆业务逻辑后面维护会非常痛苦。统一返回格式也很重要我用的Result类包含code、message和data三个字段前端判断code是否为200就可以决定要不要展示错误提示。3.2 后端包结构设计与职责划分后端工程我习惯按以下包结构组织com.example.flower ├── config │ ├── CorsConfig.java │ ├── WebMvcConfig.java │ └── JwtInterceptor.java ├── controller │ ├── FlowerController.java │ ├── OrderController.java │ └── UserController.java ├── service │ ├── FlowerService.java │ └── impl │ └── FlowerServiceImpl.java ├── mapper │ ├── FlowerMapper.java │ └── OrderMapper.java ├── entity │ ├── Flower.java │ ├── Order.java │ └── Customer.java ├── vo │ ├── LoginVO.java │ └── OrderVO.java ├── common │ ├── Result.java │ └── ResultCode.java └── FlowerApplication.javaentity对应数据库表结构vo用于向前端返回的视图对象。为什么有entity还要有vo因为实体类里的字段和数据库字段一一对应但前端可能需要额外字段比如订单列表里展示商品名称列表这个字段并不存在于订单表里。如果把多余字段塞进实体类会让数据库映射变得很混乱所以单独定义VO来承载组合数据。Service层我坚持写接口再写实现类。虽然小项目可以直接写一个类但接口抽象能避免Controller直接依赖具体实现后续替换逻辑或者做单元测试Mock都会方便很多。Mapper层直接继承MyBatis-Plus的BaseMapper基础CRUD就不用自己写了复杂SQL可以在XML文件里维护。3.3 前端目录结构与模块拆分前端工程采用Vue3 Vite创建目录结构如下src ├── api │ ├── flower.js │ ├── order.js │ └── user.js ├── assets ├── components │ ├── FlowerCard.vue │ └── Pagination.vue ├── router │ └── index.js ├── store │ ├── cart.js │ └── user.js ├── utils │ └── request.js ├── views │ ├── Login.vue │ ├── Home.vue │ ├── product │ │ ├── FlowerList.vue │ │ └── FlowerDetail.vue │ ├── cart │ │ └── Cart.vue │ ├── order │ │ └── OrderConfirm.vue │ └── admin │ ├── Dashboard.vue │ ├── FlowerManage.vue │ └── OrderManage.vue └── main.jsapi目录集中管理所有后端接口的调用每个模块对应一个JS文件比如flower.js里导出getFlowerList、addFlower等方法。这样做的好处是组件里不需要直接出现URL字符串后端改了地址只需维护一个文件。store目录用Pinia管理全局状态。购物车状态放在cart.js里用户登录信息放在user.js里。组件之间共享数据都从Store读写避免了兄弟组件层层传参的问题。utils/request.js是对Axios的二次封装统一设置基础URL、请求头、超时时间并在响应拦截器里统一处理业务码和Token过期的情况。4. 数据库设计业务落地的第一步4.1 核心表规划与关系说明数据库设计直接决定了系统能做多复杂。花店销售系统的核心表主要有这些表名作用描述category商品分类表比如鲜花、绿植、干花flower商品表保存花束名称、价格、库存、图片customer顾客表保存昵称、手机号、积分user后台用户表管理员和店员账号cart购物车表记录顾客临时选择的商品orders订单表记录订单整体信息order_item订单明细表记录订单里每一个商品条目address收货地址表记录顾客常用地址这些表之间的关系很直观一个分类下有多件花束所以flower表通过category_id关联category一个顾客有多条购物车记录和多张订单所以cart和orders分别关联customer一张订单包含多个商品条目所以order_item表以order_id和flower_id作为外键。这里要注意订单明细里的商品名称和价格要单独存一份快照不要下单之后再去关联查询flower表拿价格。因为鲜花的定价是会变的过段时间花材涨价历史订单里的价格不应该跟着变。快照设计在电商系统里非常重要虽然这只是一个花店项目但设计理念是通用的。4.2 关键表字段设计与建表SQL商品表是系统的地基字段设计如下CREATE TABLE flower ( id BIGINT PRIMARY KEY AUTO_INCREMENT, category_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, image VARCHAR(255), description TEXT, status TINYINT NOT NULL DEFAULT 1, deleted TINYINT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL, update_time DATETIME NOT NULL );价格字段必须用DECIMAL(10,2)千万不要用FLOAT或DOUBLE。浮点类型在数据库里存在精度问题订单金额算到最后多出0.01元会很尴尬。status字段表示上架状态1为上架0为下架。deleted是逻辑删除标志1表示已删除数据不真正删除方便后面恢复和分析。订单表的核心字段如下CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, customer_id BIGINT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, receiver_name VARCHAR(50), receiver_phone VARCHAR(20), receiver_address VARCHAR(255), create_time DATETIME NOT NULL, pay_time DATETIME );订单号order_no设置了唯一约束这是防止订单重复的重要手段。status字段是订单状态码0待支付、1已支付、2备货中、3已发货、4已完成、-1已取消。用数字存状态比直接存中文更规范前端只需要维护一个状态枚举映射即可。需要注意一点order是SQL关键字直接用作表名会带来很多麻烦所以一般情况下都写成orders。另外外键约束不要乱加尤其是订单明细表关联商品表如果商品被逻辑删除了外键反而会影响查询。能用索引解决的问题优先用索引外键留给数据库设计文档去描述。4.3 库存与订单的数据一致性处理花店系统最容易被答辩老师追问的就是并发问题两个人同时买最后一束花会不会超卖如果不做任何处理答案是会超卖。初级写法通常是先查库存看库存数量大于购买数量再执行更新SQL。这在单用户下没问题一旦并发到达两个请求同时查到库存为1都判断可以购买结果都去更新库存就会变成负数。正确的做法是在数据库层面做条件更新把扣减库存和库存校验变成一个原子操作UPDATE flower SET stock stock - #{num} WHERE id #{id} AND stock #{num}这条SQL通过stock #{num}条件让数据库自己判断库存是否足够更新影响行数为0就代表库存不足。配合Spring Boot的Transactional注解把扣减库存和创建订单放到同一个事务里任何一个环节出错都能整体回滚。代码里的逻辑顺序也很重要先扣库存再插入订单和订单明细。因为扣库存最容易失败先做最容易失败的事情可以减少无效操作。5. 后端核心功能实现5.1 登录鉴权JWT实现前后端身份校验传统的Session登录在前后端分离场景下有些别扭因为Session存在后端内存中跨域请求还要处理Cookie传递。使用JWT之后登录成功后后端返回一个Token字符串前端存在localStorage里之后每次请求在请求头里带上Authorization: Bearer token。JWT的生成和解析逻辑并不复杂。生成时把用户ID和角色塞进Payload用密钥签名解析时验证签名和过期时间从Token中取出用户信息。我写了一个简单的JwtUtil工具类包含generateToken和parseToken两个核心方法。接着实现一个HandlerInterceptor在preHandle方法里从请求头取Token校验失败直接返回401状态码不再继续执行业务逻辑。登录接口还要考虑密码安全问题。明文密码绝对不可取我在项目里用BCrypt对密码做哈希加密Spring Security里自带的BCryptPasswordEncoder可以直接拿来用。哪怕用户和管理员表的密码都是初始化数据也应该用加密后的版本答辩的时候解释密码加密印象分会明显不同。5.2 商品管理图片上传与回显花店系统的商品图片是关键图片不行直接劝退顾客。后台上传接口接收MultipartFile文件文件不能直接存到数据库里而是保存到磁盘目录比如D:/upload/或Linux下的/data/upload/数据库里只存相对路径/upload/xxx.jpg。上传逻辑有几个细节要注意。第一文件名必须重新生成用UUID加扩展名避免用户上传相同文件名导致覆盖。第二要限制文件类型只允许jpg、png、webp等常见图片格式防止有人上传脚本文件。第三要对文件大小做限制Spring Boot里配置spring.servlet.multipart.max-file-size5MB即可。本地目录要变成可访问的URL必须在配置里指定静态资源映射spring: web: resources: static-locations: file:D:/upload/这样配置之后访问http://localhost:8080/upload/xxx.jpg就能直接打开上传的图片。这里建议不要直接把图片保存到项目src目录下否则打包成jar之后文件不在classpath里重启或者部署还会丢失。5.3 下单接口事务、库存与订单状态流转订单创建接口是整个系统的核心Service层逻辑如下Transactional(rollbackFor Exception.class) public OrderVO createOrder(OrderCreateRequest request) { // 1. 检查重复提交根据订单号或幂等键过滤 // 2. 遍历request中的商品列表调用扣库存SQL检查影响行数 // 3. 生成订单号使用雪花算法 // 4. 保存orders记录状态为待支付 // 5. 批量保存order_item明细 // 6. 返回订单信息包括待支付金额 }Transactional注解放在Service实现类的方法上保证数据库操作要么全成功要么全失败。事务默认遇到RuntimeException才回滚所以注解里明确写了rollbackFor Exception.class把普通异常也纳入回滚范围避免扣了库存但订单没生成的情况。订单号我用Hutool的雪花算法生成格式类似1752021029388881920。如果不想引入Hutool也可以用时间戳加随机数的组合yyyyMMddHHmmss 6位随机数。但要注意并发情况下的重复问题雪花算法生成的ID基本不会重复更可靠。订单创建成功之后前端跳到支付页面这里只是模拟支付。支付接口做的事情其实很简单把订单状态从“待支付”改为“已支付”记录支付时间同时更新顾客积分。真实生产环境还需要对支付回调做幂等处理确保同一笔支付通知不会被重复处理这也是一个很好的扩展点。5.4 数据统计让经营数据变得可见统计模块是花店系统的亮点也是很多毕设容易忽略的部分。我做了三个最核心的统计接口今日销售额、订单总量、热销商品排行。今日销售额的SQL很简单SELECT IFNULL(SUM(total_amount), 0) FROM orders WHERE status IN (1, 2, 3, 4) AND pay_time CURDATE()热销商品排行需要按商品维度聚合订单明细SELECT f.name, SUM(oi.quantity) AS total_quantity FROM order_item oi JOIN flower f ON oi.flower_id f.id GROUP BY oi.flower_id ORDER BY total_quantity DESC LIMIT 10这些统计数据通过Controller的接口返回给前端前端使用ECharts的柱状图或者饼图展示效果非常直观。统计功能看起来不难但体现了对业务的思考答辩的时候能讲出为什么要统计、统计结果怎么指导补货会很有说服力。6. 前端页面与核心交互实现6.1 环境准备Vue安装及项目初始化按照当前主流方案我使用Vite创建Vue3项目。第一步是安装Node.js建议使用18或20以上的LTS版本版本太低会导致Vite启动失败。接着在命令行执行node -v npm -v npm create vitelatest flower-front执行npm create vite时会问选择框架和语言框架选择Vue语言选择JavaScript即可。项目创建之后先别急着写代码需要把核心依赖安装上npm install vue-router4 pinia axios element-plus如果觉得安装速度很慢可以把npm镜像换成国内源执行npm config set registry https://registry.npmmirror.com。很多同学在npm install阶段卡半天基本都是网络问题换完源会顺畅很多。安装完成后在main.js里注册Element Plus、Router和Pinia然后启动开发服务器npm run dev默认端口是5173浏览器打开就能看到Vite的欢迎页。开发期间前端和后端是分开跑的前端页面通过代理或者CORS访问后端的8080端口这里就涉及后面要说的跨域问题。6.2 路由与登录拦截前端路由不是简简单单的页面跳转还要承担权限控制。我通过Vue Router的beforeEach全局前置守卫来做拦截router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { return next() } if (!token) { return next(/login) } if (to.path.startsWith(/admin) localStorage.getItem(role) ! ADMIN) { return next(/) } next() })这个逻辑虽然简单但已经把“未登录不能访问”和“非管理员不能进后台”两层校验都覆盖了。用户登录成功后Token和角色信息都存到localStorage里刷新页面也不会丢失。后台管理页面的路由我设置为懒加载组件在需要时才加载减少首屏体积。实际开发中还可以根据后端返回的权限菜单动态生成路由但对花店系统来说固定路由加守卫已经够用。6.3 商品列表与购物车状态管理商品列表页是顾客进入系统后看到的第一屏需要支持分页和按分类筛选。前端调用getFlowerList接口传pageNum、pageSize、categoryId参数后端返回分页对象。页面里用Element Plus的el-table展示商品用el-pagination做分页。购物车我放在Pinia的cart.js里核心状态是一个数组每个元素包含商品ID、名称、单价、图片和数量。加入购物车的逻辑很简单function addItem(flower) { const exist items.find(item item.id flower.id) if (exist) { exist.quantity } else { items.push({ ...flower, quantity: 1 }) } }购物车数据我同步到了本地的localStorage刷新页面后数据不丢失。这里有个选择题购物车放前端还是后端放前端体验好响应快但换设备无法同步放后端可以跨设备同步但每次操作都要调接口。花店项目我选择前端本地存储这样实现简单也足够演示。6.4 订单提交与支付模拟订单提交页从购物车Store里读取已选中的商品展示确认清单让用户填写收货人、手机号和地址。提交时调用后端createOrder接口成功后会返回订单号和总金额此时跳转到支付页面。支付页面我做了模拟支付按钮点击后调用payOrder接口传入订单号。为了模拟真实场景支付接口可以延迟1到2秒返回前端用ElMessage提示支付结果然后跳转到订单列表页面。订单列表页根据订单状态显示不同的操作按钮待支付订单可以“去支付”或“取消”已支付订单可以“查看详情”已完成订单可以“删除”。这里要注意前端展示的状态文本必须和后端保持一致。我在前端维护了一个状态映射对象把后端返回的0、1、2、3、4翻译成“待支付”“已支付”“备货中”“已发货”“已完成”避免直接显示数字。7. 开发期高频问题与排查记录7.1 跨域问题前后端联调第一道坎前后端分离项目跑起来第一个拦路虎基本就是跨域。现象是浏览器控制台报“CORS policy”错误前端明明发了请求后端也收到了但响应被浏览器拦截。解决跨域有两条路。开发环境最简单的方式是使用Vite代理在vite.config.js里配置export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })前端请求的接口地址统一以/api开头Vite开发服务器把请求转发到后端的8080端口浏览器看到的还是同源请求自然没有跨域问题。生产环境则用Nginx反向代理同样可以做到路径转发。如果后端也想支持跨域可以写一个配置类实现WebMvcConfigurer允许指定来源、方法、请求头。但我个人的建议是开发环境用Vite代理更省心后端不要放开所有跨域限制否则接口等于任何人可以从任意域名调用存在安全隐患。7.2 MySQL连接与Spring Boot版本不匹配Spring Boot连接MySQL8时如果没有正确配置连接参数启动阶段会报时区错误。连接URL必须加上时区参数spring: datasource: url: jdbc:mysql://localhost:3306/flower_shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver如果driver-class-name还写com.mysql.jdbc.Driver也会报错因为MySQL8的驱动类改名为com.mysql.cj.jdbc.Driver。另外useSSLfalse可以避免MySQL8默认开启SSL导致的警告。还有一类问题来自Spring Boot版本过高。如果你创建项目时选择的Spring Boot是3.x那么要求JDK17及以上依赖里的javax.*包也换成了jakarta.*包。很多老教程代码是2.x时代的直接复制会出现编译错误。所以动手之前先确认JDK版本和Spring Boot版本匹配不要盲目追求最新版。7.3 上传文件后无法访问图片上传成功但在浏览器里访问不到经常是因为后端没有配置静态资源映射。上传接口把文件写到了磁盘目录但Spring Boot默认的静态资源目录是classpath:/static不会自动映射外部磁盘路径。解决办法有两种。第一种是在application.yml里配置spring.web.resources.static-locations把外部目录加入静态资源位置列表。第二种是自定义资源映射Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadDir); }推荐第二种方式路径可控性更强。还有一个细节Windows下文件路径使用D:/upload/这种正斜杠写法不要在拼接路径时用\否则Linux部署时会出问题。7.4 Vue依赖安装与打包的典型报错npm install时我最常遇到的是版本依赖冲突。比如Element Plus要求Vue3如果你的项目是Vue2却安装了Element Plus页面会直接白屏。创建项目时一定要看清Vue版本然后再装配套组件库。打包阶段容易出现资源路径问题。默认情况下Vite构建出的静态资源使用绝对路径/assets/如果部署在服务器根目录没问题但如果部署在子路径下就会404。解决办法是在vite.config.js里设置base: ./让资源使用相对路径。前端开发调试还有一个很实用的工具Vue DevTools浏览器插件。它可以直接查看组件树、Pinia状态和Vue Router路由信息排查“为什么数据没更新”这类问题会快很多。安装插件之后在Vue3项目中打开开发者工具就能看到Vue面板。8. 部署上线与个人经验沉淀8.1 在IDEA里运行JavaWeb项目的配置清单IDEA导入Spring Boot项目时有几个配置经常导致启动失败。第一是JDK版本要在Project Structure里把Project SDK和Java版本设置一致如果代码用了Java8的语法但SDK设置成17一般也能跑但反过来就会报版本错误。第二是Maven配置File - Settings - Maven里要指定本地的Maven仓库和settings.xml否则依赖下载不到。第三是Lombok插件一定要安装并且开启Annotation Processing否则实体类的Data注解会报找不到getter/setter。一切配置好之后运行启动类FlowerApplication看到Spring Boot的Banner打印出来Tomcat端口启动成功控制台出现“Started FlowerApplication”说明后端已经跑起来了。默认端口是8080如果端口被占用可以在application.yml里改server.port。如果引入了数据库启动前要确认MySQL服务已启动、数据库和表已创建。Spring Boot默认不会自动帮你建表需要提前执行建表SQL或者引入Flyway做迁移。花店系统我建议直接用Navicat或MySQL命令行建库建表简单直接。8.2 前后端打包与部署后端打包很简单在Maven面板里执行clean package或者终端运行mvn clean package -DskipTests打包成功后会在target目录下生成一个flower-system-0.0.1-SNAPSHOT.jar。把这个jar上传到服务器执行java -jar flower-system-0.0.1-SNAPSHOT.jar后端应用就在服务器8080端口运行了。前端先执行npm run build构建结果生成在dist目录。如果服务器上安装了Nginx把dist目录部署到Nginx静态路径下并配置反向代理把/api前缀的请求转给后端jarserver { listen 80; server_name flower.example.com; location / { root /var/www/flower/dist; index index.html; 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; } }try_files $uri $uri/ /index.html这行很关键它保证Vue Router的history模式在刷新子路由时不会404。如果不想用history模式也可以改成hash模式打包后直接双击HTML文件就能预览但URL里会带#。8.3 从做完到做好的几个习惯项目做完了工作其实才完成一半。我强烈建议花时间把接口返回统一、参数校验补齐、日志打印规范这三件事做好。很多项目能跑但一输入非法数据就报500错误这就是缺少参数校验。Spring Boot自带的Validated注解加上NotBlank、DecimalMin这些校验规则能在入口处直接挡掉无效请求。日志是排查问题最可靠的帮手在Service层关键方法打印入参和出参遇到订单创建失败可以直接看日志定位原因。不要只在报错时打印e.printStackTrace()业务日志也应该记录操作人和操作时间这对答辩演示非常有帮助。如果让我重新做一次这个项目我会把更多时间放在异常流程上比如库存不足提示、订单重复提交拦截、支付回调的幂等处理这些才是真实系统里最能体现价值的地方。花店虽小但业务闭环很完整把这一套理顺了再做电商类项目会轻松很多。这也是我做完编号047之后最想分享的一句话。