
简介这是一份基于SpringBoot与Vue全家桶开发的电商商城完整项目包含前后端源码与SQL初始化脚本适合有一定Java基础、希望学习前后端分离实战的开发者。项目集成Redis缓存、MyBatis持久层、JWT权限验证等技术覆盖用户管理、商品管理、店铺管理、订单管理、促销统计等典型模块。压缩包共1598个文件约125.68MB以Java源码、Vue的js/css/html、页面模板ftl及数据库脚本为主另有大量png/gif图片素材和依赖jar包可直接导入运行并对照学习。已有1170人学习下载是理解电商系统开发流程的实用资料。通过完整代码可掌握商品展示、购物车、订单状态流转、JWT鉴权等核心逻辑SQL脚本则提供了建表与初始数据便于快速部署和二次开发无论是学习SpringBoot整合方式还是练习Vue组件化开发都能获得完整参考也可作为毕业设计或课程项目的基础原型。1. 拿到这套Spring Boot商城源码后先别急着跑先给结论这套 Spring Boot 商城源码值钱的地方不在商品列表页而是 Redis、MyBatis、JWT、VUE全家桶这几样东西在一个真实交易项目里怎么互相咬合。压缩包里给了前后端代码和 sql 脚本数据层不用重新建模登录接口不用自己造轮子。适合三类人拿它当毕业设计的学生、想在简历上补一条完整交易闭环的转行者、接私活想快速给甲方出 demo 的工程师。我拆过的商城项目不算少包括一些付费买的资源经验是项目能不能跑起来和资源介绍里怎么写基本没关系。真正卡人的永远是数据库脚本导入顺序、Redis 地址、前端代理这些细节。下面把建库、配后端、理 JWT、起前端、修坑的全过程按顺序写一遍。2. 目录与SQL脚本五分钟看懂前后端代码到底在哪资源拿到手先别急着用 IDEA 打开。前端和后端、脚本混在一个压缩包里目录结构不先理顺后面每一步都会走偏。我先说解压和目录排查的顺序再说 SQL 导入的注意事项。2.1 从压缩包目录反推工程骨架我一般先把压缩包解压到一个干净目录里然后打印三层以内的目录结构不靠猜。命令行能看清这个包到底有几个 Maven 工程、有没有前端目录、sql 脚本是独立存在的还是写死在 resources 下。unzip springboot商城\(前后端代码sql脚本\).zip -d ./mall-proj cd mall-proj find . -maxdepth 3 -type d | sort第一行里-d是指定解压目标目录避免文件散落一地第三行的-maxdepth 3是控制目录层级层级太深会刷出一堆 target 和 node_modules反而看不到主干。排序后重点看三样东西后端模块名、前端目录名、跟 sql 或 db 相关的文件。常见结构里后端会是一个标准 Maven 工程目录长这样src/main/java/com.xxx.mall下分 controller、service、mapper 三层src/main/resources下放着application.yml前端则是一个 VUE 工程有src/views和src/apiSQL 文件要么在sql/目录要么在 doc 目录里。如果find结果里出现了两个以上的 pom.xml说明资源里可能还塞了后台管理端和前台商城端两个工程导入时别只启动其中一个就说项目是坏的。前端工程里还要注意一件事package.json和node_modules是否存在。压缩包一般不会带 node_modules这会花掉下载体积所以几乎都要自己npm install。看到没有 node_modules 很正常不是资源缺失。2.2 按顺序导入SQL先建库再导表这个资源带 sql 脚本省了手动建表的事但脚本顺序不能乱。常见的是两个文件一个 schema建库建表一个 data灌商品、会员、分类等初始化数据。如果反过来跑外键对不上直接报错。mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p mall sql/mall_schema.sql mysql -uroot -p mall sql/mall_data.sql第一条命令手动建库指定 utf8mb4 字符集。不要直接用默认 latin1 或让脚本里自己建库商城商品描述、富文本内容有很多中文和表情符号字符集不对会出现乱码搜索也查不出来。第二条导入表结构第三条灌初始化数据。如果导入时报Table already exists说明你已经跑过一次把库删了重建或者手动清空相关表别硬着头皮继续。如果脚本里自身带了CREATE DATABASE那么你就要看它里面指定的库名和 application.yml 里jdbc.url是不是同一个库名这一步不一致后端能启动但所有接口都报 404 或空指针。导完以后做个简单校验数一下表数量看看商品表有没有超过 100 条记录。这类商城资源一般都会带一批演示商品如果商品表是空的后台管理系统页面再好也看不到东西。2.3 数据库里几个容易被当成脏数据的字段商城表结构里有些事情只有你上手维护的时候才理解为什么这样设计。看脚本的时候关注这么几个字段后面排查问题全靠它们。字段常见位置实际作用deleted / is_deleted商品、订单、会员表逻辑删除标记订单列表筛选时绕不开它create_time / update_time几乎所有业务表时间戳统计订单量和导出报表时靠它status订单表、商品表订单状态机待支付/已支付/已发货/已完成先说逻辑删除。很多商城查询都会在 mapper 的 XML 里默认拼接where deleted 0。你手动在数据库删了商品表里的演示数据但没把 deleted 置为 1后台列表里它照样出现反过来如果你直接用 delete 语句删关联它的订单详情外键可能出问题。最好只用接口或把 deleted 改成 1。再有就是金额字段。脚本里商品价格一般会用 decimal(10,2)或者干脆是 int 以分存储。这两种写法都常见decimal 直观但计算时要小心精度int 分存储要留意 toFixed 舍入。我见过不少人在前端商品列表里看到 0.01 的价格第一反应是脚本脏了其实是单位没换算对。3. 后端启动链路Redis缓存、MyBatis映射与JWT过滤器的真正协作方式后端要先跑通了才谈得上联调。商城这类前后端分离项目启动后端不只是把 Spring Boot 拉起来还要让 Redis 能连上、让 MyBatis 能找到 SQL、让 JWT 过滤器放行登录。这三个环节任何一个没对齐启动都可能成功但接口全挂。3.1 application.yml 里决定启动成败的四个配置段先看后端主配置文件。这套资源里必然有application.yml或application.properties大概率不止一份dev、prod 环境拆开了。我建议只动 dev 那份别在默认配置上乱改。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 timeout: 3000ms mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true jwt: secret: mall-secret-key expire-time: 86400这里四个配置段各管一件事缺一个都会出问题。数据源的url里serverTimezoneAsia/Shanghai必须是这个值不然日期字段会出现 UTC 和本地时间差 8 小时的问题。useSSLfalse是避免本地 MySQL 没配 SSL 时报警告。Redis 配置更关键host如果不是 localhost去看看 Redis 服务到底装在哪台机器上。database: 0是选中零号库脚本里如果存了 token 或验证码和别的应用共用 Redis 时容易串键建议后面加一个业务前缀。MyBatis 的mapper-locations指向 XML 文件目录。很多商城项目 SQL 是写死在 XML 里的不是注解路径配错启动会直接报Invalid bound statement。map-underscore-to-camel-case: true是让user_name自动映射成userName不要关。JWT 这段是自定义配置不是 Spring Boot 官方配置取决于代码里有没有一个ConfigurationProperties前缀为 jwt 的类。expire-time单位是秒86400 是一天。如果项目里没有这个前缀说明 JWT 参数硬编码在代码里下面这段配置会被忽略。3.2 MyBatis 的 XML 与分页插件别把 SQL 全堆在注解里商城项目里商品列表、订单列表都是分页查询几乎都会引入 PageHelper。这个插件的用法很简单但也容易翻车。dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency版本号不要太新也别太旧。1.4.x 这一代在 Spring Boot 2.x 下表现稳定太新的版本有的需要额外适配 MyBatis 版本。Service 层典型写法是这样public PageInfoProduct listByCategory(Long categoryId, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); ListProduct list productMapper.selectByCategory(categoryId); return new PageInfo(list); }注意PageHelper.startPage只对下一条要执行的 SQL 生效。如果你在它之后又调用了别的 mapper 方法分页就被拆到那张表上去了。这是最常见的分页翻车现场。返回值用PageInfo包一层才能拿到 total、pages 这些分页元数据。XML 里的 SQL 也要留意 resultMap 和枚举字段的映射。资源里的 XML 通常已经写好 resultMap但如果你自己加字段记得同步改。这里我一般会额外看一个点商品列表 SQL 里有没有status 1的过滤条件。很多商城前台只展示上架商品后台要看到全部SQL 上就分两套 mapper 方法别在全局 SQL 上硬加条件不然后台列表永远少几条数据。3.3 JWT 过滤器每个请求都绕不开的那道检查JWT 在商城项目里一般不是靠 Spring Security 那套复杂过滤器链而是自己写一个 Filter 或拦截器。常见做法是继承OncePerRequestFilter保证一次请求只执行一次过滤逻辑。Component public class JwtAuthFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { Claims claims JwtUtil.parseToken(token.replace(Bearer , )); if (claims ! null) { request.setAttribute(userId, claims.get(userId)); } } filterChain.doFilter(request, response); } }这段逻辑的核心是请求到了任何接口之前先从 Header 里取出Authorization把 Bearer 前缀剥掉解析 token把 userId 塞进 request 属性。后端的 controller 方法里通过RequestAttribute(userId) Long userId就能拿到当前登录人。需要注意放行规则。登录接口、注册接口、商品轮播图这类前台接口如果也走过滤器但没有 token 就全部被拦那前端连登录页都进不去。很多项目里过滤器只做解析、不做拦截拦截动作交给了 Spring 的拦截器或注解哪个接口需要登录就加一个RequireLogin之类的注解。拿到资源后先去找到这个过滤器注册的路径匹配规则确认/api/order这类接口必须带 token/api/login放行。这样才能保证登录后下单链路是通的。Redis 在 JWT 链路里的角色容易被忽视。纯 JWT 是无状态的服务端没法主动让某个 token 失效但商城后台需要「退出登录后 token 立即作废」的效果。常见做法是把签发时间或 token 本身存进 Redis退出时删除。如果你发现项目里登录接口会往 Redis 里塞一个带过期时间的 key那就是这个套路。注意 Redis key 的过期时间要和 JWT 的 expire-time 保持一致否则会出现 token 合法但 Redis 里 already 没了的状态。4. 前端VUE项目对接从登录到下单的token全链路后端接口通了以后前端才能真正“长”起来。VUE 项目里最核心的不是页面长什么样而是 axios 怎么把 token 带上、路由守卫怎么判断能不能进、以及刷新页面后登录态会不会丢。这三件事是前后端分离项目联调时最容易吵架的地方。4.1 axios 统一实例与拦截器token 别散落在每个页面里我看到很多新手在每一个VUE页面里都写一份axios.get的调用token 是手拷进去的。登录态一变要找十几个文件改。正确的做法是建一个统一 axios 实例在拦截器里注入认证信息。import axios from axios const service axios.create({ baseURL: /api, timeout: 5000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) export default servicebaseURL: /api用的是相对路径后面的转发交给前端开发服务器代理这样在开发和部署阶段都能通过调整代理配置来切换后端地址不用改业务代码。timeout设成 5000 毫秒下单接口如果超过 5 秒就该查后端或者 Redis 了不要无限等。拦截器里检查localStorage.getItem(token)这一步保证每个从商城前端发出的请求只要本地有 token就自动带上有Bearer前缀的 Authorization。后端过滤器和它配合才形成完整的认证闭环。token 不要直接写在组件里也不要塞进 Vuex 就算完刷新页面 Vuex 全丢localStorage 才是持久化的最终落点。4.2 路由守卫与状态持久化刷新之后登录态不能丢配合上面的拦截器前端路由还需要一道关卡。用户没登录时访问购物车、结算页要跳回登录页登录之后再去登录页则要跳出提示。router.beforeEach((to, from, next) { const token localStorage.getItem(token) const whitelist [/login, /register, /home, /product] if (whitelist.indexOf(to.path) ! -1) { next() } else if (!token to.path.indexOf(/rec) 0) { next(/login) } else { next() } })路由守卫里whitelist是放行页面登录页、注册页和商品浏览页可以不用登录直接看to.path.indexOf(/rec) 0这类判断用于会员中心、后台管理这类受保护路径。这里要和后端 JWT 过滤器的放行路径保持一致前端放开接口但后端拦截就出现 401前端拦截但后端放开就会出现越权访问。登录态持久化还有一个容易漏的点每次刷新页面后用户信息存的是 localStorage 里的一份 JSON但购物车数量这类全局状态存在 Vuex 里。刷新后 Vuex 清空购物车角标变成 0这是经典翻车现场。修复方法是在 App 初始化时用Vuex的store.commit从 localStorage 重新载入用户信息和购物车数量常见做法是写一个initData()在created里执行。4.3 联调时和后端对不上的三类接口小坑前后端分开跑的阶段接口对不上是最耗时间的这三类问题我建议后端启动后立刻自查一遍。第一个是实时跨域CORS问题。前端在 8081 端口启动后端在 8080VUE 开发服务器把/api代理到 8080 后跨域基本不存在。但如果没起代理、直接在 axios 里写http://localhost:8080绝对路径浏览器会拦截所有响应现象是控制台 CORS 报错、登录接口一直 401。解决方式一般有两种前端配置代理或者后端加一个CorsFilter允许指定来源。先看资源里有没有这两种方案中的任何一种两个都没有就自己补一个代理配置别去后端凑合允许所有来源。第二个是时间字段格式。后端 LocalDateTime 默认序列化出来是2024-05-01T10:30:00前端列出订单时间直接显示一个带 T 的字符串很丑。通常需要后端加一个 Jackson 全局配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8如果配置完订单详情接口的时间还是带 T说明 JSON 序列化用的是 Fastjson 而不是 Jackson得去代码里找 Fastjson 配置类加SerializeConfig。第三个是金额字段精度。后台接口返回 0.01 还是 1取决于后端是否把分转成元。联调时要先确认一个商品的原价和促销价前端下单结算要和数据库里的值完全对上差一分钱都要在接口层找原因。5. 避坑指南本地复现这套商城时最常碰见的五个问题这条是血泪经验汇总。下面的每一条我都踩过按“现象 → 原因 → 解决”写清楚大家在复现时照着排除就行。5.1 环境与依赖JDK 版本反噬、MySQL 函数不兼容问题一项目在本地启动时编译报错或者启动后立刻闪退控制台出现AbstractMethodError或NoSuchMethodError。原因这套资源大概率是 Spring Boot 2.x 的代码当初开发环境是 JDK 8。你电脑装了 JDK 17 甚至更高版本某些老第三方库在新 JDK 下换了实现类直接链路断裂。这类问题不是代码写错了是环境不兼容。解决先看后端 pom.xml 里java.version按它的要求切换 JDK。IDEA 里File - Project Structure - Project SDK换成 JDK 8。同时把 Maven 的 JRE 设置也改成 JDK 8否则 IDEA 编译用的还是高版本。如果是 Spring Boot 3.x 的代码那 JDK 8 反而不支持认准 Boot 版本再定 JDK。问题二导入 sql 脚本时报某个 MySQL 函数不存在比如ST_Distance_Sphere或自定义函数fn_get_distance。原因脚本在另一个 MySQL 版本上写好用了高版本函数或自定义函数你的 MySQL 版本不满足。MySQL 8.0 里的有些空间函数在 5.7 缺失反过来脚本里的ENCRYPT这类老函数在 MySQL 8 被移除了。解决先确认安装的 MySQL 主版本。如果你是 5.7看脚本里有没有 MySQL 8 特有写法如果你是 8.0看有没有老的加密函数。遇到这种不兼容别整段删掉去脚本里找到具体报错的行把函数替换成等价写法或者注释掉初始化数据里依赖该函数的记录。商品演示数据少几条不影响跑通。5.2 启动与联调端口打偏、Swagger 冲突、富文本被转义问题三前端登录成功但登录后所有接口全部 401后端日志显示请求进来了但 token 校验失败。原因排查出来通常有两个方向。一是前端代理把/api转发到了别的端口比如前端开发服务器是 8081代理却写死了target: http://localhost:9090而后端实际跑在 8080二是 axios 拦截器没生效token 没拼到Authorization上后端过滤器和注册了「permitAll」的路径本来不该拦截的接口跟着一起 401。解决先看vue.config.js或vite.config.js。proxy: { /api: { target: http://localhost:8080, changeOrigin: true } }把 target 改为后端实际端口。再打开浏览器开发者工具找到订单接口的请求头确认Authorization: Bearer xxx存在。token 带了还是 401就去后端排查过滤器注册时的urlPatterns和shouldNotFilter方法。问题四后端启动时报Failed to start bean documentationPluginsBootstrapper或者swagger页面打开是空白。原因资源里同时引入了 springfox 2.x 和 springdoc-openapi或者 springfox 版本和 Spring Boot 2.6 的路径匹配策略冲突了。严格说这个是依赖残留不是核心业务问题但启动都起不来后面没法玩。解决优先关掉其中一个。如果只想要接口调试保留 springdoc 时在application.yml加springdoc.api-docs.enabledfalse可以关掉文档接口先把业务跑起来。如果要保留 swagger 页面把另一个 starter 依赖从 pom.xml 里注释掉。这类依赖问题不解决IntelliJ 里是永远不会给你好脸色的。问题五后台编辑商品富文本详情后保存出来的 HTML 全部变成了转义字符前端渲染成一堆标签文本。原因项目里加了一个全局 XSS 过滤器对上传内容做了转义这是安全策略但富文本本身就要传 HTML全局过滤把所有接口都拦了。常见于同时处理文件上传和富文本内容的老项目。解决找到那个全局过滤器配置类里面一般有一个白名单数组把富文本接口的路径加进去或者给过滤器加一个paramName排除参数。拿这种项目练手正好可以把安全过滤和白名单机制一并看会。6. 把登录链路完整走一遍不做Swagger也能验收这套商城项目跑起来后很多人会点开 Swagger 页面去测接口。Swagger 能用但它不适合验证「token 在真实请求里怎么流动」这件事通过带 token 的 curl 手动走一条链路对整体系统的理解更直观。先确认后端、Redis、MySQL 三个进程都正常。后端启动日志里出现Tomcat started on port 8080Redis 通过redis-cli ping能返回 PONGMySQL 通过mysql -uroot -p -e select count(*) from mall.product能查询到记录。三个条件满足后开始模拟用户操作。第一步请求登录接口拿 tokencurl -X POST http://localhost:8080/api/login \ -H Content-Type: application/json \ -d {username:admin,password:123456}这个接口的路径和参数名从后端 LoginController 里看不同资源实现略有差别。返回的 JSON 里一般有token字段复制下来放到下面的请求里。第二步带 token 查商品列表curl -X GET http://localhost:8080/api/product/list?pageNum1pageSize10 \ -H Authorization: Bearer token注意token必须替换成上一步返回的真实值。返回里能同时看到商品数据和total说明分页正常。这时再故意去掉 Authorization 头请求一遍如果得到 401说明 JWT 过滤器真正在起作用如果还能返回数据说明该接口没被保护权限设置需要再看。第三步模拟加购。这个动作往往依赖userId后端是从 token 的 claims 里解析出来的。加购成功后去 Redis 里面看一眼购物车里是否生成了 keyredis-cli keys *cart*能看到购物车 key说明项目里购物车可能存在 Redis看不到也有可能是存在 MySQL 订单表里以数据库实际结构为准。我一般习惯以这一步来判断项目对缓存的使用程度方便后面做改造。从那以后我每次拿到这类前后端分离的商城资源都会强制走一遍这条链路登录、带 token 查数据、不带 token 验证拦截、加购看落库位置。整个过程用不了十五分钟但对项目技术栈的理解比看任何说明文档都直接。这套资源恰好把 Redis、MyBatis、JWT 这整套链路都串好了按这个流程走完你基本就能摸清它的每一个关键节点。希望帮到你。本文还有配套的精品资源点击获取