☰
SpringBoot+Vue服装商城系统开发:前后端分离电商实战解析
2026/10/13 4:14:28 网站建设 项目流程

这几年帮人参谋毕业设计,看得最多的就是电商类系统。说实话,十个里面至少有六七个都是“XX商城”、“XX购物平台”,技术栈也高度雷同,无外乎SpringBoot配Vue,外加一个MySQL。但你真去细看实现,差距能拉到非常大。有的项目表结构设计得清清爽爽,接口逻辑一看就懂;有的则把业务全堆在Controller里,一个方法写几百行,购物车逻辑还在用Session硬扛,这种项目不管答辩老师问不问细节,自己后面维护都会崩溃。

今天我想借一个比较典型的选题——基于SpringBoot与Vue的服装产品购物商城,把这类前后端分离电商项目从立项到落地过程中的核心设计思路、关键代码实现、常见坑点完整梳理一遍。这个选题覆盖了用户注册登录、商品分类浏览、购物车管理、订单生成、支付流程模拟、后台商品管理这些电商标配功能,非常适合用来串起SpringBoot + Vue + MySQL这条主流技术链。不管你是正在选毕业设计题目,还是想自己练手写一个完整的全栈项目,这篇文章都应该能给你省下不少瞎折腾的时间。

1. 项目整体设计与架构拆解

1.1 为什么选前后端分离架构

很多同学在做这类系统时,最先纠结的就是用不用前后端分离。我的建议很直接:除非你有特殊理由,否则直接上分离架构,没有悬念。

所谓前后端分离,说白了就是前端只管页面渲染和用户交互,后端只管业务逻辑和数据接口,两边通过HTTP协议下的JSON数据通信。前端跑在自己的开发服务器上,后端跑在自己的应用服务器上,互不干扰。

适用到服装购物商城这个场景,前后端分离带来的好处是实实在在的:

首先是开发效率上的提升。前端工程师可以在Mock数据下先把页面做出来,后端工程师可以同步写接口,不需要像JSP时代那样等一方做完另一方才能接手。哪怕你是一个人单干,分离架构也能让你先画页面、再写接口,心智负担比大杂烩式的模板渲染低得多。

其次是维护成本。商城类项目后期迭代频繁——加个优惠券弹窗、改个商品列表样式、调整分类导航,这些都是前端改动,不需要动后端代码。反过来,后端调整价格计算逻辑,前端也不需要重新部署。

第三是部署灵活。前端编译成静态文件丢到Nginx里,后端打包成Jar包扔到服务器上,两边各自独立扩容。就算访问量上来了,前端静态资源走CDN也能分摊掉大量压力。

1.2 技术栈选型背后的考量

先明确一下整个项目的基础技术组合,这也是SpringBoot + Vue系项目最标准、最稳的一套配置。

后端方面,SpringBoot 2.x是绝对的主力。选它的原因无非三点:自动配置省掉了大量XML配置,内嵌Tomcat让部署变成一个java -jar命令的事,生态成熟什么第三方库都能无缝接进来。配合MyBatis-Plus做ORM层,CRUD几乎不用手写SQL,分页查询直接用内置的Page对象就行。数据库用MySQL 5.7或8.0,稳定而且网上能查到的资料最多,出了问题不愁找不到解决方案。

前端方面,Vue 2 + Element UI是这些年的主流搭配。Vue 2上手平缓,响应式数据绑定、组件化开发、路由管理这些概念清晰直观;Element UI组件库覆盖了电商后台需要的表格、表单、弹窗、导航菜单等绝大部分场景,自己不用去手写CSS组件。虽然Vue 3 + Element Plus已经是趋势,但如果你是为了稳妥完成一个毕业设计,Vue 2的资料量和踩坑文档反而更丰富,遇到问题搜解决方案更容易。

前端脚手架用Vue CLI,搭建项目只需几条命令。状态管理用Vuex,路由用Vue Router,HTTP请求用Axios。这套组合基本是Vue生态的标准动作,没有多少需要额外纠结的。

做这类项目,我最想提醒的一句话是:不要过度设计。有些人为了显得技术含量高,非要引入微服务、Redis缓存、消息队列、ElasticSearch,结果光是搭建环境就花了两周,核心业务还没开始写。对于电商商城这个规模的项目,单体应用加一个MySQL完全足够,重点是把你用到的每一层的原理讲清楚,而不是把一堆没跑通的技术名词堆在论文里。

1.3 功能模块规划

服装电商系统表面上看着简单,但功能拆开之后,牵扯到的模块一点也不少。我在做类似项目时的习惯是先画功能脑图,再逐个模块去实现。

整个商城系统按用户角色可以分为两个端:

用户端(前台商城页面):

  • 用户注册与登录(含验证码校验)
  • 商品分类展示与按分类浏览
  • 商品详情查看(图片、价格、库存、描述)
  • 商品搜索(支持关键字模糊查询)
  • 购物车管理(添加商品、修改数量、删除、清空)
  • 订单确认与提交
  • 在线支付(一般用模拟支付即可)
  • 个人中心(个人信息修改、我的订单、收货地址管理)

管理端(后台管理页面):

  • 管理员登录
  • 商品管理(添加、编辑、删除、上下架)
  • 商品分类管理
  • 订单管理(订单列表、发货、查看详情)
  • 用户管理(用户列表、禁用/启用)
  • 数据统计(销售量统计、分类占比等简单图表展示)

这里要特别强调一个容易忽略但非常重要的点:功能清单要跟着数据库表结构走。很多同学把前端页面画完了才发现后面需要的字段没有建到表里,来回改表改代码,非常痛苦。先确定要哪些表、每张表有哪些字段,再开发页面,效率会高很多。

2. 数据库设计与核心表结构讲解

数据库设计是整个项目的底盘,底盘打不好,后面写代码处处别扭。尤其电商这种业务,表与表之间的关联复杂,字段设计得更要仔细推敲。

2.1 核心表清单

一个标准的服装商城系统,最少需要下面这些表:

  • 用户表(user):存储普通用户信息和管理员信息。我习惯加一个role字段区分角色,0表示管理员,1表示普通用户,省得再拆一张admin表。

  • 商品分类表(category):存储服装分类,比如男装、女装、童装、配饰等。字段至少要包含分类名称、父级ID。加上父级ID字段,后续如果想做二级分类,不用重新建表。

  • 商品表(product):这是核心中的核心。字段包括商品名称、分类ID、商品主图、价格、库存、销量、详情描述、上架状态、创建时间。服装类商品还要考虑颜色、尺码这些属性,不过如果做简化版本,可以把这些属性放到详情描述里,或者用单独的规格表,看你打算把项目做成什么复杂程度。

  • 购物车表(cart_item):关联用户和商品,字段包括用户ID、商品ID、商品数量、选中状态、加入时间。加选中状态是为了兼容那种购物车中可以勾选多种商品一起结算的真实体验。

  • 订单表(order):存储订单核心信息,字段包括订单编号、用户ID、订单总金额、收货人姓名、联系电话、收货地址、订单状态、创建时间。订单状态一般用数字表示,比如0待付款、1待发货、2已发货、3已完成、4已取消。

  • 订单明细表(order_item):一个订单包含多种商品,所以要把订单和商品的关系拆到这张表里。字段包括订单ID、商品ID、商品名称(冗余存储)、商品图片(冗余存储)、购买单价、购买数量。

  • 收货地址表(address):字段包括用户ID、收货人姓名、联系电话、省份、城市、详细地址、是否默认。

2.2 关键表设计细节

我重点展开一下订单相关表的设计。

订单表创建时有两个细节值得注意。一是订单编号不要用自增ID,要生成一个唯一订单号,一般采用时间戳加随机数的方式,比如202506071320150001,后面加四位随机数,保证并发下单时不重复。二是金额字段建议用decimal(10,2)类型,不要用float或double。浮点数在计算金额时会出现精度丢失,比如0.1加0.2得到0.30000000000000004,这在支付场景里是完全不能接受的。

订单明细表一定要冗余商品名称和商品图片。为什么?因为商品表里的商品随时可能被修改甚至删除,如果明细表只存商品ID,订单历史详情就拿不到当时购买的商品信息了。订单作为交易凭证,其数据是不可变更的,所以要把下单那一刻的商品快照数据保存下来。这个思路在真实电商系统里是标配,做项目时提前设计好,答辩时也是个可以拿出来讲的加分点。

购物车表唯一约束要加在(用户ID, 商品ID)上。这样同一个用户往购物车加同一件商品时,如果记录已存在,就直接更新数量而不是再插一行,避免购物车数据膨胀。有了这个约束,后端新增购物车逻辑就变成了“查一下记录是否存在,存在则更新数量,不存在则新增”。

2.3 表关系与字段冗余的取舍

我见过一些同学把表设计得特别“规范”,每个字段都去外键关联,结果查询的时候要JOIN四五张表,性能差不说,逻辑还极其绕。在这个项目里,我更建议采用适度冗余的方式。

商品表里存分类名称,订单明细表里存商品名称和图片,地址表里存完整的收货人信息。这样做的原因是电商场景是读多写少,展示订单列表、商品列表时,你并不想每次都去关联查询另一张表拿一个名称字段。以空间换查询效率,在数据量不大的毕业设计里非常划算。

下面给一个商品表的建表SQL参考:

CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL COMMENT '分类ID', `name` varchar(100) NOT NULL COMMENT '商品名称', `main_image` varchar(255) DEFAULT NULL COMMENT '商品主图', `detail` text COMMENT '商品详情', `price` decimal(10,2) NOT NULL COMMENT '商品价格', `stock` int(11) NOT NULL DEFAULT 0 COMMENT '库存', `sales` int(11) NOT NULL DEFAULT 0 COMMENT '销量', `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '上下架状态:1上架 0下架', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

建立idx_category_id索引是因为前台按分类筛选商品是最高频操作,走索引才能保证查询效率。索引不是越多越好,但针对高频查询字段建索引是必备习惯。

3. 后端核心实现:接口设计与业务逻辑

后端这里,我挑几个含金量比较高的点来讲:统一的返回结果封装、登录鉴权的Token设计、订单生成的事务控制逻辑。

3.1 统一返回结果封装

前后端分离模式下,接口返回格式必须统一,不然前端处理异常情况会非常崩溃。我最常用的封装类是一个泛型Result<T>,包含三个字段:code状态码、message提示信息、data具体数据。

public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }

实际项目中还会定义业务异常类,比如库存不足、订单不存在,统一抛出后在全局异常处理器里转换成对应的Result往回返。这样Controller里的代码会非常干净,业务逻辑里只管处理正常流程,异常情况交给异常机制处理。前端Axios响应拦截器里判断code不等于200时统一提示message,也省了每个页面单独写错误处理。

3.2 用户登录与JWT鉴权

电商商城肯定要处理登录状态。之前的传统做法是Session加Cookie,但在前后端分离架构下,Session跨域处理很麻烦,我强烈建议直接用JWT(JSON Web Token)。

JWT的本质是服务端把用户信息加密签名后生成一串Token返回给前端,前端每次请求时在请求头里带上这串Token,服务端再校验Token的合法性和有效期。它的核心好处是无状态,服务端不需要存Session,天然支持横向扩展,多个后端实例都能校验同一个Token。

我用的是jjwt库,登录成功后生成Token的核心代码大致是:

String token = Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim("username", user.getUsername()) .setExpiration(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();

这里的secretKey是自定义的密钥,生产环境应该放到配置文件中,不要硬编码在代码里。有效期我设置的是2小时,对于毕业设计足够了。

后端还需要写一个拦截器,在进入需要登录的接口前校验Token。这里的逻辑是读写请求头里的Authorization字段,如果为空或校验失败,直接返回401状态码,前端收到后跳转到登录页。用SpringBoot写拦截器相当简单,继承HandlerInterceptor接口,在preHandle方法里写校验逻辑,再注册到WebMvcConfigurer中,指定拦截路径即可。比如/order/**、/cart/**开头的是需要登录的接口,/admin/**开头的还需要额外校验管理员角色。

3.3 下单流程的事务与库存控制

下单是电商系统最核心、也最容易出问题的业务环节。前端传来一个购物车ID列表或商品ID列表,后端就需要完成下面这一串操作:

  1. 根据商品ID列表查出所有商品信息
  2. 校验每个商品的库存是否充足
  3. 逐一扣减库存
  4. 增加商品销量
  5. 删除对应购物车记录
  6. 创建订单主表记录
  7. 创建订单明细记录
  8. 返回订单ID或订单号

这个流程里,第2步到第7步必须放在同一个数据库事务里,要么全部成功,要么全部回滚。想象一下:用户提交订单时校验库存发现不够,前端页面刷新了一下又重复提交,结果库存被扣了两次,订单却没生成成功——这就是没有事务控制导致的严重数据问题。

加事务非常简单,Controller调用的Service方法上标注@Transactional注解即可,Service层代码如下:

@Transactional public Long createOrder(Long userId, List<Long> cartItemIds) { // 1. 查询购物车项 List<CartItem> cartItems = cartItemMapper.selectList(...); // 2. 封装商品列表与数量,计算总金额 BigDecimal totalAmount = BigDecimal.ZERO; Map<Long, Integer> productCountMap = new HashMap<>(); for (CartItem item : cartItems) { Product product = productMapper.selectById(item.getProductId()); if (product.getStock() < item.getQuantity()) { throw new BusinessException("商品[" + product.getName() + "]库存不足"); } totalAmount = totalAmount.add(product.getPrice().multiply(new BigDecimal(item.getQuantity()))); productCountMap.put(product.getId(), item.getQuantity()); } // 3. 生成订单号并插入订单表 Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(0); orderMapper.insert(order); // 4. 插入订单明细 for (CartItem item : cartItems) { Product product = productMapper.selectById(item.getProductId()); OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(product.getId()); orderItem.setProductName(product.getName()); orderItem.setProductImage(product.getMainImage()); orderItem.setPrice(product.getPrice()); orderItem.setQuantity(item.getQuantity()); orderItemMapper.insert(orderItem); // 5. 扣库存、增销量 productMapper.reduceStock(product.getId(), item.getQuantity()); productMapper.addSales(product.getId(), item.getQuantity()); } // 6. 删除购物车记录 cartItemMapper.deleteBatchIds(cartItemIds); return order.getId(); }

需要提醒一个细节:商品价格应该从数据库查询获取,而不是信任前端传来的单价。如果前端下单时把商品单价传给后端直接用,那用户改一下请求参数就能超低价买东西。正确做法是后端根据商品ID从数据库查出实时价格,再计算订单总价。这个点既关系到系统安全性,答辩时提出来也是加分项。

还有一个问题值得提前说:真正的高并发秒杀场景下,先查库存再扣减不是线程安全的。两个用户同时下单,都查到库存余1,然后都去扣,最后库存会变成负数。毕业设计一般不会遇到真正的并发压力,但你可以在扣库存的SQL里加上库存大于0的条件来保证安全:

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

这样如果库存不足,受影响行数为0,就可以判断并回滚整个事务了。

3.4 支付模块的处理思路

说句实在话,毕业设计里接入真实的支付宝或微信支付,既涉及企业资质,又需要沙箱环境调试,大部分学校根本不强制。我的建议是使用模拟支付:订单状态从0(待付款)变成1(待发货),同时记录支付时间。

但模拟支付不等于随便做一个按钮。你可以让前端弹出一个支付对话框,显示应付款金额,用户点击确认支付后,前端调用后端/order/pay接口,后端把这个接口标记为需要登录,执行状态变更的逻辑。这样既完成了业务闭环,又在论文中说明了真实环境下应该对接第三方支付平台,以及支付回调接口的设计思路,反而显得你考虑得更周全。

支付回调这一点真的值得多写几笔。如果对接真实支付平台,支付成功之后是支付平台主动调用你的回调接口通知你订单已支付,而不是让前端调用后端去改状态。因为前端完全可能被伪造。对于模拟支付这种简化方案,你可以在答辩时主动说明这一点,证明你明白真实支付流程的差异。

4. 前端页面设计与交互实现

4.1 前台页面的整体布局

前台商城页面风格建议走简洁路线,服装类商品的视觉呈现比功能堆砌更重要,所以页面整体布局要清爽、留白充足,商品图要大而清晰。

页面我分为三个区域来实现:

  • 顶部导航栏:Logo、分类导航、搜索框、购物车入口、用户中心入口。
  • 内容区域:首页轮播图、热卖商品推荐、分类商品列表。
  • 底部版权区:普通信息展示即可。

Vue Router的router-view把页面分成多个路由组件,用户点击分类导航时,路由跳转到分类商品列表页,同时把分类ID通过路由参数传递过去,后端根据分类ID查询对应商品。首页商品推荐调用的是后端的/product/hot接口,按销量倒序或者按创建时间倒序取前8件。

4.2 商品展示列表组件

商品列表的经典实现是卡片式排列,使用Element UI的el-card组件展示每个商品,配合el-row和el-col实现响应式栅格布局,让商品在不同屏幕宽度下自动换行排列。

商品列表页需要考虑分页加载。前端通过el-pagination组件发送页码请求,后端用MyBatis-Plus的分页插件返回Page<Product>对象。这个对象里封装了当前页数据、总记录数、总页数等信息,前端根据这些信息渲染分页导航。

@Override public Page<Product> pageProducts(int pageNum, int pageSize, Long categoryId, String keyword) { Page<Product> page = new Page<>(pageNum, pageSize); 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, 1).orderByDesc(Product::getSales); return productMapper.selectPage(page, wrapper); }

这里用LambdaQueryWrapper构建条件式查询,每个条件都是可选的,既支持分类筛选又支持关键字搜索,前端传什么就查什么,非常灵活。

4.3 购物车与订单交互流程

购物车页面是前端交互逻辑最多的地方。用户改数量时,前端要实时计算该商品的小计金额和购物车总金额;勾选或取消选择商品时,总金额也要随之变化。用Vue的computed计算属性来处理这些派生值,代码简洁且性能可靠。

下单时前端要把选中的购物车项ID列表传到后端,同时收货地址也要一并提交或提前在个人中心维护好默认地址。我的实现是直接在个人中心里维护地址列表,下单页面让用户从已有地址中选择,这样既符合真实场景,又比提交订单时现场填写地址体验更好。

订单页提交后,前端跳转到订单详情页或等待支付页面。如果是模拟支付,可以在这里展示一个假的二维码图片,提示用户扫码并确认支付,两秒后轮询后端查订单状态是否已变为待发货,再跳转到成功页面。

4.4 后台管理界面的搭建

后台管理系统用Vue + Element UI的后台布局模板非常方便,左侧是菜单栏,右侧是内容区。菜单项包括商品管理、分类管理、订单管理、用户管理等,每个菜单项对应一个路由组件。

商品管理的难点在于图片上传。本地开发时,前端要把图片传给后端,后端存到本地的/upload/images目录下,再把访问URL返回给前端。这里需要给SpringBoot配置静态资源映射,并且注意上传目录不要放在编译后的classpath里,否则重新打包时图片会被清掉。我习惯把上传路径配置成外部绝对路径,比如D:/upload/,然后配置虚拟路径映射:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourcePattern("file:" + "D:/upload/"); } }

这样数据库里存的是/upload/xxx.jpg,浏览器可以直接访问。如果是在线部署,还需要把上传路径改成服务器上的绝对路径,并且后台要限制图片的类型和大小。真实项目中一般还会把图片传到对象存储服务,但毕业设计直接用本地存储就够了,答辩时提一句”生产环境应采用第三方对象存储”即可。

订单管理页面需要注意表格中订单状态最好用标签去展示,比如待付款显示为info标签,待发货显示为warning标签。后端最好返回状态码同时返回状态描述文本,前端根据状态码决定标签类型。这样展示更友好,状态的变化也一目了然。

5. 常见问题与排查技巧实录

这部分我专门说我以及带过的同学在写同类项目时遇到频率最高的问题,每个问题的排查思路和解决方式都很有实战价值。

5.1 后端跨域请求失败

前后端分离项目最常见的第一道坎就是跨域。前端跑在localhost:8080,后端跑在localhost:9090,两个端口不同,浏览器默认不允许跨域请求。

现象是前端发出请求后,浏览器控制台报错Access to XMLHttpRequest at ... has been blocked by CORS policy,接口数据完全拿不到。

解决方案很成熟,SpringBoot里写一个跨域配置类即可:

@Configuration 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); } }

需要注意,allowCredentials(true)表示允许携带Cookie,但此时allowedOriginPatterns("*")不能用*字面量,否则会和Credentials冲突,这是很多新手踩过的坑。如果前端用Token认证的话,请求头里没有Cookie,一般不需要启用allowCredentials,直接全放开也可以。

另外,就算后端配好了跨域,实际开发时强烈建议前端统一配置Axios的baseURL,不要每个请求都写全路径,然后在Vue CLI配置文件里配置开发代理:

module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:9090', changeOrigin: true, pathRewrite: { '^/api': '' } } } } }

开发时使用代理转发,后端和前端完全不需要考虑跨域问题,代码里所有请求都以/api开头。这种做法的好处是部署到生产环境时,前端静态文件由Nginx托管,Nginx也可以把/api请求反向代理到后端服务,代码无需修改。

5.2 前端拿不到后端返回的数据

另一个高频问题:页面能发出请求,但前端拿到的数据字段不是自己想要的,比如后端返回的是createTime,前端却一直用createTime访问得到undefined。

这个问题的根源通常是后端实体类的Java驼峰命名法转成了数据库字段的下划线命名法,JSON序列化时也会映射成驼峰格式,但前端模板里写错了字段名。排查思路很简单:打开浏览器开发者工具的网络面板,看接口返回的JSON结构,对照着前端代码检查字段名,而不是凭空猜测。

如果是Java实体里字段命名不规范、配置了@JsonProperty,也可能导致返回字段与数据库字段不一致,检查一下实体类的注解即可。

另外一个非常常见的问题就是数据库时间为null。前端表格里显示整列空白,往往不是数据真的没有,而是后端没有对null值做处理,前端就直接渲染了null。后端处理方式可以在实体类的时间字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解,这样时间格式统一且不会有莫名其妙的格式问题。

5.3 库存和订单数据不一致

后台手动修改商品价格,用户订单金额却没有变化;或者商品删除后,订单详情页图片显示不了。这些问题的根源都在于订单明细表没有做数据快照。

我在前面特意强调过,订单明细表要冗余商品名称、图片、价格,目的就在这里。如果你创建订单明细时字段是从商品表实时查出来的,那么商品后来改了名称、换了图、改了价,甚至被删除,历史订单都会跟着变,这在真实电商系统里是不可接受的。

所以排查订单数据不一致问题时,先去看订单明细表里的字段是否冗余了快照数据。没有冗余的,改设计;有冗余但数据不对的,看创建订单时的赋值逻辑。

5.4 上传的图片刷新后丢失

后台管理里商品图片上传成功后能正常显示,但是重启后端或者重新打包之后,图片全部变成了404。

这个问题我在前面也提过,就是上传路径的问题。默认情况下如果你把图片保存到项目根目录/target/classes/static/upload下,打包时target目录会被清理重建,图片自然就丢了。

解决方法非常简单粗暴:不要把上传路径放在项目编译目录里,配置成外部独立目录,比如D:/upload或者Linux服务器上的/home/app/upload,前端访问时通过静态资源映射来读取。数据库里存的是相对URL路径,而不是绝对路径,这样项目迁移时只需要改配置文件的路径常量即可。

5.5 常见问题速查表

我把这个项目里遇到的高频问题整理成一个速查表,方便直接对照排查:

问题现象可能原因解决方式
前端请求接口报302或redirected后端拦截器拦截了未登录请求并做了重定向拦截器直接返回401状态码,前端根据状态码跳转登录页
用户能直接通过接口修改他人订单接口没做用户归属校验订单操作接口先从Token中获取当前用户ID,再校验订单归属
商品价格小数计算后有误差使用了float或double类型金额统一使用BigDecimal,数据库字段用decimal
前端表单提交后中文乱码请求或响应编码不一致后端配置字符编码过滤器为UTF-8,数据库连接串加characterEncoding=UTF-8
分页总数不对分页参数没传或Page对象使用错误检查前端是否传了pageNum和pageSize,后端使用MyBatis-Plus正确配置分页插件
修改商品后前台不显示缓存或商品状态为下架该架构没有缓存,检查商品status是否为1

6. 项目部署与环境搭建备忘

6.1 开发环境准备清单

开源项目最怕环境配不对。我按顺序列一份完整的环境清单,照着准备基本不会出错:

  • JDK 1.8:SpringBoot 2.x的最佳搭档,不要用17,部分兼容性问题处理起来麻烦。
  • Maven 3.6+:用来管理后端依赖,IDE里自带也行。
  • MySQL 5.7或8.0:本地安装即可,注意字符集设置为utf8mb4,否则商品详情里的特殊字符或表情符号会报错。
  • Redis:如果只在登录鉴权部分没用上就不需要安装。前端页面如果有验证码刷新或Session校验需求,引入Redis也是自然的。但这个项目如果用了JWT,核心业务不需要Redis,没必要硬上。
  • Node.js 14+:Vue CLI 4/5依赖的Node版本有要求,版本太高有时会有兼容问题,尽量按项目脚手架文档的要求来。
  • Vue CLI:全局安装,用于创建Vue工程。
  • Navicat或SQLyog:可视化操作MySQL,建库建表更直观。

6.2 生产环境部署流程

如果你打算把项目部署到服务器上展示,部署流程基本是这样:

前端先运行npm run build,编译后生成dist目录,这是纯静态文件。把dist目录下的内容上传到服务器Nginx的html目录中,然后配置Nginx把接口请求转发到后端:

server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:9090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

这个配置里的try_files $uri $uri/ /index.html一定要加,否则前端用Vue Router的history模式时,刷新非首页路由会直接404。有没有这一行是很多新手部署后才发现的差异。

后端部署更简单,在服务器上装好JDK和MySQL,打包后执行:

nohup java -jar shopping-mall.jar --spring.profiles.active=prod > app.log 2>&1 &

配一个独立的application-prod.yml文件,把数据库连接地址改成服务器上的,上传路径改成服务器路径,端口改成9090(或直接用默认8080,注意别和Nginx冲突),启动完直接访问域名即可。

6.3 数据库初始化脚本编排

数据库脚本一定要跟着代码一起提交到版本控制工具里,不要只在本地建表。我建议把SQL脚本分成三个文件:

  • schema.sql:建表语句
  • data.sql:初始数据(管理员账号、默认分类、示例商品)
  • menu.sql:后台管理菜单初始化数据(如果有权限模块才需要)

这样别人clone下来之后,只需要执行一次数据库脚本就能跑起整个项目,对答辩演示和联调阶段的队友都特别友好。我自己带过的学生里,能坚持做到这一点的人,后期几乎没遇到过因为“数据库怎么搭”而来的求助消息。

数据初始化时,注意管理员密码不要用明文,直接在SQL里写MD5加密后的字符串,比如admin123加密存成e10adc3949ba59abbe56e057f20f883e。登录校验时对用户输入做同样的MD5后再比对,密码不透明存储是基本素质,写进论文也是安全意识的体现。

7. 我给初次做这类项目的人几条实在建议

项目做到现在,前面该讲的架构和代码都讲得差不多了。最后我想从经验层面说几句可能比代码更值钱的话。

第一个建议是先跑通全链路再优化细节。很多人的毛病是一上来非要研究Vue Router怎么嵌套路由、Element UI主题怎么自定义,结果注册登录这条主链路都没走通。正确顺序应该是:先搭好后端骨架能登录、能查商品,再搭前端页面能把商品展示出来,购物车,下单,支付,一个闭环跑通了再慢慢补后台管理、优化页面样式。全链路通了,这个项目就已经保底及格了。

第二个建议是把项目拆成里程碑来完成。比如第一个里程碑做注册登录加商品列表,第二个里程碑做购物车和下单,第三个里程碑做后台管理。每完成一个里程碑就提交一次代码并写几行说明,既能控制进度,答辩时导出的提交记录和开发日志也会很好看。

第三个建议是认真做好项目README。把项目介绍了、技术栈、如何启动、功能清单、演示账号全部写清楚。不要小看这个文件,答辩老师翻阅项目仓库时,第一眼看到的就是README,一份清晰完整的README会让老师觉得整个项目都很专业。

第四个建议是主动把项目部署到公网服务器,给老师一个可以随时访问的演示地址。很多答辩环境网络受限,PPT演示时现场跑项目容易翻车,但是一个提前部署好的线上链接比什么都稳。一台低配云服务器一年也用不了多少钱,关键是省心。

最后想分享一个小技巧:做这类全栈项目,遇到报错最忌讳盯着屏幕反复猜。一定要学会看日志、看浏览器开发者工具、看数据库实际数据。后端报错看控制台堆栈,前端报错看Network面板,数据不对直接查表。这个流程走下来,百分之九十的问题都能自己定位。

这个服装商城项目本身不算难,但麻雀虽小五脏俱全。把它做完,你基本就掌握了前后端分离开发的标准套路,也能把SpringBoot和Vue的核心机制吃透。把这个底子打好,后面往后台管理系统、小程序商城、社区团购这些方向上扩展,也只是一层窗户纸的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询