这两年接到不少学员和同行问同一个问题:想做一个完整的电商类全栈项目练手,但不知道从什么业务切入比较合适。我的建议一直很明确——别一上来就抄一个庞大的商超系统,那种项目对新手来说信息量太大,反而什么都学不扎实。做一个小零食商铺系统刚刚好:业务链路完整,从商品展示到下单支付再到订单状态流转都能覆盖;并发压力不至于大到需要极端优化,又能让你把分布式会话、缓存、接口幂等等真实问题都碰到。这篇就完整聊聊我基于SpringBoot和Vue从零构建一套小零食商铺系统的设计与实现过程,包括数据库建模、后端核心模块拆分、前端页面与接口对接、常见坑点排查,以及最终部署上线时的一些取舍。
这套系统适合谁?如果你正在学SpringBoot和Vue,需要一个能写进简历的完整项目;或者你已经在工作中写了不少CRUD,但对“一个完整电商闭环”还缺乏整体认知,那这篇文章可以给你一条比较踏实的参考路径。文中不会只贴代码,重点放在每个环节的为什么和怎么排查。
1. 整体设计思路与业务需求拆解
1.1 小零食商铺的业务闭环到底有哪些环节
不要把“小零食系统”想得太简单。虽然商品单价低、SKU少,但一个标准在线零食商铺的核心链路和大型电商没有本质区别,只是复杂度降了一档。把它拆开看,至少包含这几条主线:
- 用户端:访客浏览商品列表、查看商品详情、加入购物车、提交订单、在线支付(或模拟支付)、查看个人订单列表与订单详情、确认收货。
- 管理端:运营人员维护商品分类、维护商品信息(包含图片上传)、上下架管理、处理订单(发货、查看退款申请)、查看基础销售统计。
- 基础设施:用户登录注册、访问权限控制、购物车数据存储、订单号生成、库存扣减、文件存储与访问、统一返回结构、全局异常处理。
很多教程会把重心全部放在用户端的前台界面好不好看上,但实际设计和实现时,管理端才是业务规则最密集、开发工作量最大的部分。商品状态流转、订单状态机、库存操作权限,这些都属于管理端的逻辑边界。因此设计阶段就要把两套端的功能列表梳理清楚,否则开发到一半很容易陷入“前台做完了,后台不知道点什么菜单”的窘境。
1.2 技术栈选型与分工边界:为什么是SpringBoot和Vue
技术选型没有绝对优劣,只看适不适合这个项目的规模和团队情况。我推荐SpringBoot + Vue的组合,理由很实在:
- SpringBoot 极大简化了Spring生态的配置成本,内嵌容器使得本地开发一个main方法就能启动,非常适合中小型项目和教学演示。同时它天然支持Spring Security、Redis、MyBatis等生态组件,后续扩展支付接口、消息队列时不需要更换框架。
- Vue(这里我采用的是Vue 2 + Element UI 方案,当然你也可以用Vue 3 + Element Plus,下文统一以Vue 2叙述)具备清晰的双向数据绑定和组件化开发模式,配合现成的桌面端UI组件库,能快速搭建出风格统一的管理后台和商城前台页面。
- 前后端分离的开发方式,让后端只负责输出API,前端只负责渲染与交互,接口文档一旦定好,两边可以并行开发,项目周期大幅缩短。
这套组合在学校课程设计、个人毕设和中小公司内部系统中出现频率非常高,生态成熟、资料丰富,遇到问题基本都能搜到解决方案。对于从业者来说,掌握这套技术框架的协作方式也是进入企业后比较常见的技能组合。
1.3 模块划分与架构分层策略
我按照“单体应用 + 模块化代码结构”的方式来组织后端,而不是拆微服务。原因很简单:零食商铺系统的业务量级远没到需要微服务的程度,引入微服务只是徒增运维和部署成本。但模块化还是要做的,否则代码全堆在Controller和Service里,后期分分钟爆炸。
后端分成几个核心包:controller、service、mapper、entity、dto、vo、config、common、utils。关键约束是:
- Controller层只做参数接收、参数校验、调用Service、返回统一结果,不允许写业务逻辑。
- Service层承载业务规则,比如下单时校验库存、计算金额、生成订单号、扣减库存,这一系列操作必须在一个事务里。
- Mapper层只做数据库访问,复杂SQL尽量在XML中维护,避免注解SQL串过长难以维护。
- DTO负责接收前端传参,VO负责输出给前端页面,二者都不允许直接裸露数据库实体。
前端也分为用户端和管理端两个独立工程,或者在一个工程里通过路由权限区分。我更倾向于一个工程、两套布局路由的方式,这样共用axios封装、路由守卫、工具函数,减少重复代码。
前端目录按功能拆分:api(请求接口)、views(页面组件)、router(路由配置)、store(状态管理)、components(公共组件)、utils(公共方法)。每个页面组件尽量只依赖对应的api模块,禁止在页面里直接写请求路径。
2. 数据库设计与核心表结构解析
2.1 分类、商品、用户这三张基础表怎么设计
数据库是系统的地基,表设计不好,后面写业务代码全是泪。零食商铺的核心表包括用户表、分类表、商品表、购物车表、订单表、订单项表,此外还有轮播图表、收货地址表(视需求可加)。
先看用户表,核心字段:
- id:主键,自增。
- username:登录名,唯一索引。
- password:加密后的密码,我使用BCrypt加密,不允许明文存储。
- nickname:昵称,用于页面显示。
- avatar:头像路径。
- phone:手机号。
- role:角色标识,0表示普通用户,1表示管理员。
分类表比较简单,字段为id、分类名称、排序值、显示状态。需要注意分类一般只做两级,不要设计成无限级分类,否则管理端交互复杂度会上涨不少。
商品表是重点。字段包括id、分类ID、商品名称、商品主图、商品轮播图、商品详情描述、价格(以元为单位,可用DECIMAL(10,2))、库存、销量、是否上架、是否推荐、创建时间、更新时间。这里有几个关键经验:
- 价格字段必须使用DECIMAL,不要用FLOAT或DOUBLE,浮点数在金额计算中会产生精度问题,这个坑太多人踩过。
- 库存字段建议单独建一张库存表或商品表中加一个version字段用于乐观锁控制,后续并发下单时需要用到。
- 商品图片使用相对路径保存,不直接存Base64字符串或网络完整地址,方便后续将文件服务迁移到对象存储或CDN。
2.2 购物车和订单表的设计细节
购物车表最简单:id、用户ID、商品ID、商品数量、加入时间。唯一约束(user_id, product_id)即可,避免一个用户对同一个商品产生多条购物车记录。
订单相关表设计是整个系统的核心难点。订单主表字段:id、订单编号、用户ID、订单总金额、实付金额、优惠金额(如果有)、收货人姓名、联系电话、收货地址、订单状态、支付时间、发货时间、收货时间、创建时间。订单项表:id、订单ID、商品ID、商品名称(下单时的快照)、商品图片(快照)、商品单价(快照)、购买数量、小计金额。
这里有个重要设计决策:订单项必须冗余商品的名称、图片、价格快照。为什么?因为商品表里的信息是会被运营修改的,如果不做快照,用户查历史订单时看到的商品名和价格可能已经和下单时完全不一致,这在电商业务中是不允许的。这一点在设计之初就要想清楚。
订单状态我用整数表示,配合状态机流转:0待支付、1待发货(已支付)、2待收货(已发货)、3已完成(已收货)、4已取消(用户主动取消或超时取消)。退款场景可以再加一个5退款/售后状态,但如果要简化,也可以暂时不做完整的退款流程,而是将退款单作为订单的一个子状态。
2.3 索引策略和冗余字段的取舍
数据量不大不代表可以不建索引。我的原则是对所有外键关联字段和查询条件字段创建索引。用户ID、订单编号、商品分类ID、商品名称(前缀模糊查询场景)都要加索引。订单编号要建唯一索引,因为它是对外暴露的单号,不允许重复。
冗余字段方面,商品表的销量字段就是冗余设计,它可以通过订单表实时聚合得到,但线上店铺频繁展示销量列表,实时聚合性能糟糕,所以直接维护一个销量字段,下单成功后在事务里对销量做累加。这种以少量冗余换取查询性能的做法在业务系统里非常普遍。
3. 后端核心模块实现与关键技术细节
3.1 统一返回结构、全局异常与参数校验
接口设计的规范程度直接决定前后端联调效率。我定义了一个统一返回类,结构为code(状态码,200为成功)、message(提示信息)、data(业务数据)。所有Controller接口一律返回这个结构,前端axios拦截器只认这个结构,非200则弹出错误提示。
全局异常处理使用@RestControllerAdvice配合@ExceptionHandler。重点处理三类异常:业务异常(手动抛出的BizException)、参数校验异常(MethodArgumentNotValidException)、兜底异常(Exception)。这样做的好处是:后端不需要在每个Controller里写大量try-catch,遇到业务错误只需抛异常并附上错误原因,前端就能收到格式一致的错误响应。
参数校验方面,我使用javax.validation注解配合@Validated,在DTO字段上加@NotBlank、@NotNull、@Min等注解。比如注册接口需要校验用户名非空、密码长度6-20位等。这套机制比手写if判断简洁得多,也让代码规范度提升一个档次。
3.2 基于JWT的登录认证与接口权限控制
登录功能在教学中经常被弱化,但在完整项目中必须认真处理。我这里采用JWT方案,流程如下:
- 用户输入用户名密码,后端校验通过后生成JWT令牌。
- JWT中包含用户ID、用户名、角色信息,并设置合理的过期时间(一般两小时,也可以做成刷新令牌机制,但初版先不做那么复杂)。
- 前端拿到令牌后存到localStorage,并在axios请求拦截器中把token放到请求头Authorization字段。
- 后端通过Spring Security或拦截器解析token,鉴权通过则放行请求。
我使用的是拦截器方案而非常规熟人推荐的Spring Security。不是说Security不好,而是初版项目需要快速搭建,Security配置复杂度较高,而且它配合JWT必须要写一堆过滤器链配置。自己写HandlerInterceptor解析token,逻辑透明、代码量小,对于零食商铺这个量级完全够用。
管理中涉及到权限的接口需要在拦截器中校验角色。比如修改商品的接口必须要求角色为管理员,普通用户访问则返回403业务码。这些可以在注解上设计一个@RequireRole标记,环绕AOP判断角色,这样Controller方法只需要加注解就能完成权限控制。
3.3 商品管理、文件上传与图片存储方案
商品管理端最核心的操作是商品的增删改查和上下架。后端实现时要注意几点:
- 新增和修改商品需要处理商品主图和轮播图列表。前端上传图片后返回图片路径,前端把路径集合传给后端的VO里。
- 商品列表接口要支持分页、名称模糊搜索、分类筛选、上下架筛选。后端使用MyBatis-Plus的Page对象配合条件构造器QueryWrapper即可完成,不需要手写复杂XML。
- 下架操作需要校验该商品是否存在未处理的订单,避免用户下单后商品被下架导致订单不可履约。这个校验逻辑比较容易忽略,我建议在Service层加上。
图片上传我使用的是本地服务器存储方案(存储到项目外部静态资源目录),通过WebMvcConfigurer配置虚拟路径映射。生产环境如果有条件,可以直接替换为云对象存储,代码改动集中在上传工具类,其余逻辑不受影响。
3.4 购物车与订单提交的并发与事务处理
购物车加购和改数量相对简单,核心是给用户ID和商品ID做唯一约束,插入使用on duplicate key update语法,避免先查询在插入的竞态条件。
订单提交是整个系统最核心的接口,涉及多张表的写入操作,必须保证原子性。表结构设计完成之后,订单接口的实现逻辑如下:
前端提交数据包含:收货信息ID、购物车商品ID列表、备注。后端Service在一个事务方法内完成以下步骤:
- 根据购物车ID列表查询商品明细,同时校验商品是否上下架、库存是否充足。
- 计算订单总金额(必须由后端计算,不能信任前端传的价格)。
- 生成订单编号,我使用的规则是:年月日时分秒 + 随机数,足够保证单商户短时间内的唯一性。
- 插入订单表和订单项表。
- 扣减商品库存,使用乐观锁机制防止超卖。
- 清空对应的购物车记录。
- 累加商品销量。
事务方法加上@Transactional注解,默认遇到RuntimeException回滚。这里有一个经验:扣减库存的SQL要使用UPDATE product SET stock = stock - #{count} WHERE id = #{id} AND stock >= #{count}这种形式,通过受影响行数为0判断库存不足,然后抛出异常触发回滚,这比先查库存再更新更安全。
3.5 订单状态流转与超时取消的设计取舍
订单状态的流转实现不难,难在超时未支付订单如何处理。方案按复杂度排序有三种:
- 最简单:用户下单时不真正锁定库存,只有在支付成功后才扣减库存。但这样存在超卖风险,比如有10件库存、20个用户同时下单,支付阶段就有20个订单都认为有货,实际上只能支付成功10个。这种方案适合并发极低的场景。
- 推荐:下单即锁定库存,通过定时任务扫描超时未支付订单并取消,释放库存。这个方案在中小项目中很实用。
- 进阶:引入延迟队列(Redis过期键监听或消息队列延迟消息)处理超时订单。但延迟队列对初学者来说坑点较多,消息丢失、重复消费都可能出现。
零食商铺系统我采用的是第二种方案,在订单表上增加一个订单过期时间字段,定时任务每分钟扫描一次,关闭超时未支付的订单并回补库存。定时任务在单体应用中使用Spring的@Scheduled就能实现,不需要额外引入框架。
4. 前端页面结构与核心功能实现
4.1 前端工程初始化与路由设计
Vue前端工程我使用Vue CLI创建。初始化的同时直接安装好vue-router、vuex、axios和element-ui,这些是做中后台项目几乎必装的依赖。
路由设计采用两套布局模式。用户端路由包括:首页商品列表、商品分类页、商品详情页、购物车页、订单结算页、支付结果页、个人中心、我的订单页。管理端路由包括:登录页、管理台首页(数据统计)、商品管理、分类管理、订单管理。
路由配置中设置全局前置守卫,判断访问页面是否需要登录。如果需要且当前无token,则重定向到登录页。管理端页面额外判断角色字段是否等于1,不是管理员则直接提示无权限。
4.2 首页、商品列表与商品详情的渲染优化
首页我采用自定义布局而不直接使用组件库的现成模板,因为零食电商的首页需要突出商品图片和促销氛围。顶部是导航栏和搜索框,中间是轮播图,下面分成几个商品模块:新品上架、推荐零食、热销排行。这些模块的数据来源于后端不同的商品列表接口,用商品表和筛选条件组合即可得到。
商品列表页的核心是分类筛选和分页加载。这里的交互细节较多:左侧分类树,点击不同分类切换商品列表,分页组件绑定当前页码和每页数量,分类切换时重置为第一页。前端需要做好loading状态,避免数据返回慢导致页面卡顿感。
商品详情页除了展示基本信息外,关键功能是图片轮播、加入购物车和立即购买。加入购物车接口要传商品ID和数量,如果用户未登录则需要跳转登录页。立即购买按钮做的其实是先清空购物车中对应商品或直接跳转下单页并携带商品ID参数。
4.3 购物车与订单结算页的数据联动
购物车页面在电商系统中的体验非常重要。我采用前端本地维护购物车数据的展示,但每次渲染页面时从后端拉取最新的商品状态(尤其是价格和库存),避免用户看到的价格与实际结算时不一致。
购物车中修改数量后,前端立即重新计算小计和总价,同时在数量变化后调用后端接口更新数据库中购物车商品数量。这里有一个优化经验:数量变化不要每个数字都调用接口,而是在输入框失焦或点击减号按钮时才发送请求,防止高频率请求把接口打爆。
订单结算页需要展示购物车中选择的商品列表、总金额、收货地址、备注。提交订单后跳转到支付页面,这个项目我采用模拟支付逻辑,点击“模拟支付成功”按钮修改订单状态为已支付。如果要接入真实支付平台如某支付产品的扫码支付,需要申请商户号并编写异步回调接口,这部分逻辑可以独立成一个方法,方便以后扩展。
4.4 管理端商品与订单管理界面
管理端页面设计相对直接,核心是表格操作和数据表单。商品管理页包含:搜索栏(商品名、分类、上下架状态)、商品表格(图片、名称、价格、库存、销量、状态)、新增/编辑弹窗、上下架按钮、删除按钮。新增/编辑弹窗中图片上传使用Element UI的el-upload,上传完成后后端返回图片路径,前端把路径存进表单数据的imgList数组中。
订单管理页展示所有订单,支持按订单状态筛选。订单详情需要弹窗展示完整订单信息,包括订单项列表、收货人信息、金额明细、操作记录。管理员可以对订单进行发货操作,发货后需要填写物流单号并更新订单状态。
管理端的统计页面我实现的是简化版:今日销售额、总订单数、总用户数、商品总数、近7日销售趋势图。销售趋势图使用ECharts实现,后端提供一个按日期聚合销售额的统计接口,前端将数据填入折线图即可。
5. 完整实操过程记录与关键环节说明
5.1 开发环境准备与项目骨架搭建
建议的开发环境版本组合:
- JDK 1.8或11(新项目也可以用17,但要注意MyBatis-Plus等依赖版本兼容)。
- Maven 3.6+。
- MySQL 5.7+或8.0。
- Redis 5+(这里用于session存储和缓存,如果不想依赖Redis,也可以用内存存储替换,文章默认使用Redis)。
- Node.js 14+,Vue CLI 4/5。
后端项目创建完成后,第一步就是配置application.yml,核心配置项包括数据源连接信息、MyBatis-Plus的mapper扫描路径和日志输出、文件上传的存储路径。我习惯把环境相关的配置拆成dev和prod两份,便于本地调试和服务器部署。
前端创建完成后,第一件事是把axios封装好。在utils目录下创建request.js,统一设置baseURL、请求超时时间、请求头token注入、响应拦截器中的code判断和错误提示。这里有基础内容但也很重要:统一封装axios可以减少大量重复代码,同时方便日后修改接口基础路径。
5.2 后端接口开发顺序和联调注意事项
开发顺序建议按照业务依赖关系来确定,我一般这样排:用户注册登录模块 → 商品分类模块 → 商品模块 → 文件上传 → 轮播图 → 购物车模块 → 订单模块 → 统计模块。先搭好基础的查询列表,再慢慢叠加状态变更类的写接口。
每个模块的开发流程都是先设计数据库表,再写实体类,接着写Mapper接口和XML,再写Service接口和实现类,最后写Controller。接口写完可以使用Postman先自测,重点测试参数校验是否生效、异常返回是否合理、事务是否正常回滚,自测通过后再和前端对接。
前后端联调时的两个常见痛点:一是字段命名不一致,后端习惯用驼峰,前端有时候用下划线或者直接使用后端返回的字段名,解决方式是设计DTO/VO时严格定义输出字段名;二是时间格式不一致,后端返回的时间是Date类型,JSON序列化后可能出现类似"2024-01-01T00:00:00.000+00:00"的格式,前端展示需要格式化,这里建议后端在全局统一配置日期格式化格式,避免前端逐个字段处理。
5.3 商品图片上传与虚拟路径映射的细节
图片上传这块我多说几句,因为容易踩坑。后端实现的上传接口接收MultipartFile,校验文件大小和类型后,将文件写入服务器硬盘目录,例如/home/app/upload/。文件命名使用UUID前缀加原始文件名后缀,避免中文文件名和重名问题。
前端访问图片时请求地址是/upload/xxx.jpg。但upload目录并不存在于SpringBoot项目的classpath下,因此必须配置静态资源映射:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceMapping("file:" + uploadPath); } }注意生产环境中uploadPath必须是服务器上的绝对路径,并且要给到运行Java进程的用户读写权限。另外,前端上传组件的接口响应数据需要调整成Element UI期望的格式,或者设置before-upload回调自己处理返回结构。
5.4 订单超时定时任务与库存回补的实现
@Scheduled定时任务的写法和实现都挺简单,但里面有两个坑需要注意。
第一个坑:定时任务默认是单线程串行执行的,如果有多个定时任务同时启用,可能会互相阻塞。解决方案是在配置类中实现SchedulingConfigurer,或者直接在启动类中通过@EnableScheduling和@EnableAsync配置线程池。
第二个坑:订单超时取消和库存回补的定时任务要与下单事务隔离开,扫描出超时订单后逐条处理,并且要使用for update行级锁锁定订单记录,防止同一订单被两次取消处理。这里我给出一个简单参考:
// 扫描超过30分钟未支付的订单,且状态为待支付 List<Order> expireOrders = orderMapper.selectExpiredOrders(); for (Order order : expireOrders) { // 使用行锁更新订单状态 int result = orderMapper.updateStatusById(order.getId(), 4); if (result > 0) { // 回补库存 productMapper.increaseStock(order.getOrderItems()); } }5.5 打包部署上线时的环境配置与运行
本地开发完成后,部署到服务器是个需要细心处理的过程。前端执行npm run build打包生成dist目录,将dist里的静态文件交给Nginx管理。后端使用Maven打成jar包,在服务器上直接运行。
Nginx需要配置两个关键块:静态资源服务和反向代理。将前端请求的/api前缀反向代理到SpringBoot应用端口,其他路径访问前端静态资源。同时还需要配置客户端请求体大小限制,否则上传大图片时会报413错误:
server { listen 80; server_name your-domain.com; client_max_body_size 20m; location / { root /usr/share/nginx/html; 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; } location /upload/ { alias /home/app/upload/; } }后端启动时建议使用nohup java -jar xxx.jar --spring.profiles.active=prod > app.log 2>&1 &方式,日志文件独立保存。定期清理日志和磁盘空间是运维环节很容易忽略的小事,但积累久了可能成为大麻烦。
6. 常见问题排查与踩坑记录实录
6.1 MyBatis-Plus的FieldStrategy问题
使用MyBatis-Plus时,默认的更新策略是忽略null字段不更新。这导致一个经典问题:如果前端传过来的字段中某个值为空字符串或null,数据库的对应字段可能不会被更新成空值。尤其是商品修改时,如果某个字段被清空,前端传null但数据库未更新,页面刷新后发现修改不生效。
解决方案有两种:一是数据库字段设置默认值,只接受新增时使用;二是使用MyBatis-Plus提供的UpdateWrapper,明确指定要更新的字段;三是全局配置field-strategy的更新策略为IGNORED。建议不要全局修改,因为会导致很多字段被误更新成null,推荐用UpdateWrapper精准控制。
6.2 金额精度与Double的坑
金额字段如果在前端使用JavaScript的浮点数运算,会出现0.1+0.2不等于0.3的问题。展示金额时强烈建议前端只做展示,不在浏览器端做金额加减乘除运算。如果要在前端做计算,使用封装好的整数分单位运算库处理。
后端在计算订单金额时也要注意:商品单价乘以数量得到小计,多个小计求和得到总金额,必须全部使用BigDecimal,禁止使用double。大数字和小数运算的精度损失在金额场景是不可接受的。这里我习惯封装一个金额工具类,包含加法、乘法、格式化等常用方法。
6.3 跨域问题的完整处理方案
前后端分离开发时,跨域是几乎所有新手都会遇到的问题。浏览器同源策略导致前端页面直接访问后端接口会被拦截。解决思路不外乎两种:
- 后端启动CORS全局配置,允许指定域名跨域访问。开发环境可以在SpringBoot配置类中设置addCorsMappings,允许所有来源访问/api路径。
- 生产环境使用Nginx反向代理,前端请求同源路径/api,由Nginx转发到后端服务,从根本上规避跨域问题。这种方式推荐在生产环境使用,更安全也更灵活。
在开发时前端Vue CLI的devServer也可以配置proxy,将请求代理到后端端口,实现开发阶段无跨域体验。这是前端工程的调整,不需要后端改动。
6.4 数据库连接池连接泄漏
在项目调试过程中,出现过一个数据库连接池耗尽的问题。表象是后端运行一段时间后接口响应全部超时,日志报HikariPool连接不可用。排查后发现是某处查询数据库的代码在异常分支上没有关闭连接,导致连接泄漏。
这个问题的根因要归到代码质量问题。使用Spring + MyBatis时,常规的数据库连接管理是由框架托管的,但如果手写了JDBC代码(比如某些特殊的批量操作),就需要在finally块中释放连接、语句和结果集。排查时可以使用HikariCP的监控指标,查看当前活跃连接数和空闲连接数,快速定位是否存在连接泄漏。
6.5 前端页面白屏和路由刷新404问题
前端部署到Nginx后,刷新某个子路由页面出现404,是SPA应用非常常见的坑。原因是浏览器请求的URL路径后端Nginx没有对应的文件,Nginx返回了404。
解决方法就是前面Nginx配置中的try_files $uri $uri/ /index.html;,将未匹配的路径统一回退到index.html,由前端路由接管。这个配置在部署任何Vue/React应用时都是标配,我在这里再次强调,防止部署时忘记。
另一个排查白屏的方向是检查前端静态资源的加载路径。如果打包时没有配置publicPath为相对路径,服务器上资源会从根路径引用,而部署在子目录时就会全部404。建议终端在构建时使用publicPath: './'或根据实际部署环境设置。
7. 避坑心得与后续扩展建议
7.1 关于项目设计阶段的三条体会
首先,做系统设计时不要只围着技术转。拿到需求先画业务流程图,把用户下单、支付、发货、收货这条主线完整走通,再开始建表和写代码。我见过太多项目一开始就纠结用哪个权限框架,结果业务流程都说不清楚的情况。技术方案永远是为业务服务的。
其次,坚持“接口返回结构先行”。前后端约定好统一结构后,后端先写好空接口返回假数据,前端可以立刻开工,两边并行开发效率比串行高很多。这也是很多公司采用接口文档工具(比如Swagger)的原因,即使没有工具,口头约定好结构也必须做到。
最后,必要的地方写注释,但不要为写而写。注释解释的是为什么这么写,而不是这段代码做了什么。代码可读性靠的是命名规范和结构清晰,注释只是锦上添花。
7.2 后续可以扩展的方向和优先级
这套系统完成后,如果想继续精进,我建议按以下优先级扩展:
- 接入真实支付(如果申请到商户渠道),需要实现支付回调接口和订单状态同步。
- 增加商品评价功能,涉及上传评价图片、评价后追加晒图和追评、商家回复评价。
- 增加优惠券模块,包括发放优惠券、下单抵扣、过期失效定时任务。
- 引入Redis缓存商品列表和商品详情,学习缓存穿透、缓存击穿、缓存雪崩处理。
- 引入消息队列处理订单超时和异步通知场景,提升对削峰填谷和最终一致性的认知。
这些扩展方向都建立在现有代码结构之上,也说明这套系统的延展性足够好。但我不建议一上来就全部加上,每一块都值得单独钻研,贪多嚼不烂。
7.3 一个提升开发效率的小习惯
最后说一个我在实际开发中的小习惯:每天开发前先花五分钟把当天要做的事情拆成小任务,逐个完成后打勾。这听起来很基础,但能有效避免“看似忙了一天,实际上什么都没做完”的状态。尤其是这类多模块项目,顺序一旦乱了,联调阶段会非常痛苦。
写代码之外,我还会在每次完成一个模块后做一次简单的自测清单:这个接口的参数校验是否覆盖、异常是否返回统一格式、事务是否能正确回滚、上线后是否会有明显性能瓶颈。这套自测习惯坚持久了,项目的bug率会明显下降。希望这篇文章对正在做类似全栈项目的你有所帮助,有任何更好的方案或踩坑经验,也欢迎在评论区一起交流。