☰
谷粒商城全栈项目实战:Spring Boot + MyBatis-Plus 电商系统快速搭建与避坑指南
2026/10/16 3:24:22 网站建设 项目流程

简介:这份资源是谷粒商城项目的完整技术压缩包,面向正在学习Java Web开发、准备电商项目实战的开发者与在校学生,帮助其一次性获取前端界面、后端服务与数据库脚本,省去零散搜集的麻烦。包内以zip格式打包,整体约127.7MB,涵盖SQL脚本、HTML页面及后端代码等主要类型:SQL部分涉及商品、订单、用户等表结构设计、初始化数据与查询脚本,HTML则对应商城首页、购物车、登录注册等交互界面,后端代码负责API设计、业务逻辑与数据库增删改查。目前已有416人学习下载,适合作为课程设计或毕业设计的参考素材。通过研读这套代码,读者可以理清电商系统从数据库建模到前后端联调的完整链路,掌握分页查询、事务处理、权限控制等常见实现思路,并借助现成的表结构与页面快速搭建可运行的原型,在对照调试中积累排错经验。

1. 从一份电商全栈压缩包说起:谷粒商城能帮你省掉多少造轮子的时间

如果你正在找一个能直接跑起来的电商全栈项目练手,或者公司要快速搭一套商品管理后台,那这份「谷粒商城 代码和sql以及html.zip」大概率能让你少熬几个通宵。它不是那种只给几个接口的玩具 Demo,而是把后端服务、数据库建表脚本、前端页面三件套打包在一起,解压就能看到一条完整的商品管理链路。适合谁?刚学完 Spring Boot 想找个真实业务练手的开发者,或者需要快速交付一个带后台管理功能的电商原型的小团队。我拿到包的第一件事是翻 SQL 文件,因为一个电商项目能不能跑起来,八成取决于表结构设计得合不合理,剩下的两成才是代码里的玄学 Bug。

2. 技术栈与目录结构:先搞清楚包里到底装了什么

2.1 从压缩包到可运行工程:目录层级速览

解压之后,目录结构大致是这样的(不同版本可能略有出入,但核心分层不会变):

gulimall/ ├── gulimall-product/ # 商品服务模块 │ ├── src/main/java/ # 业务代码 │ ├── src/main/resources/ │ │ ├── application.yml # 数据源、端口配置 │ │ └── mapper/ # MyBatis XML 映射文件 │ └── pom.xml ├── gulimall-common/ # 公共依赖与工具类 ├── gulimall-gateway/ # 网关模块(部分版本包含) ├── sql/ │ ├── gulimall_pms.sql # 商品库建表 + 初始数据 │ ├── gulimall_oms.sql # 订单库 │ └── gulimall_ums.sql # 用户库 ├── html/ │ ├── index.html # 商城首页静态页 │ ├── product.html # 商品列表页 │ └── admin/ # 后台管理页面 └── README.md

这个分层是典型的微服务拆分思路:商品、订单、用户各管各的库,通过公共模块共享实体类和工具方法。gulimall-common里通常放着统一返回结果R、分页工具PageUtils、常量类这些每个模块都要用的东西。我第一次跑的时候没注意common模块要先 install 到本地仓库,结果 product 模块一直报找不到依赖,这个坑后面会细说。

2.2 后端技术选型:为什么是 Spring Boot + MyBatis-Plus

从pom.xml里能读出这套项目的技术底座:

技术版本区间作用
Spring Boot2.x基础框架,自动装配
MyBatis-Plus3.xORM 层,单表 CRUD 不用写 SQL
MySQL5.7 / 8.0数据存储
Thymeleaf / 静态 HTML—前端页面渲染
Maven3.6+依赖管理与构建

选 MyBatis-Plus 而不是原生 MyBatis,核心原因是商品模块有大量单表增删改查——比如商品分类的树形查询、品牌列表分页,这些用BaseMapper直接调selectList就完事,省掉大量 XML。但复杂关联查询(比如商品关联分类、品牌、属性)还是得手写 SQL,所以mapper目录下依然有 XML 文件。这种「单表靠 Plus、多表靠 XML」的混合模式,在实际项目里很常见,也是我比较推荐新手先掌握的组合。

2.3 前端页面组织:静态 HTML 怎么和后端对接

html目录下是纯静态页面,没有用 Vue 或 React 脚手架。首页index.html里通过fetch或axios调后端接口拿商品数据,渲染到 DOM 上。后台管理页面admin/下通常是表格 + 表单的结构,用 Bootstrap 或 Element UI 的 CDN 版本快速搭出来的。

这种「后端渲染 + 静态页 AJAX」的方式,好处是部署简单——把 html 丢到 Nginx 或者 Spring Boot 的static目录下就能访问,不需要单独跑 Node 服务。坏处是页面多了之后状态管理会乱,但对于一个教学性质的项目来说,够用且直观。我一般会先把index.html里的接口地址改成自己后端的 IP 和端口,再启动服务,不然页面打开就是一片空白,控制台一堆跨域报错。

3. 数据库脚本拆解:SQL 文件里藏着整个业务模型

3.1 建表语句逐段读:商品、订单、用户三库分离

打开sql/gulimall_pms.sql,前几十行是建库和建表语句。核心表包括:

  • pms_product:商品主表,字段有product_id、product_name、price、brand_id、category_id、create_time等
  • pms_brand:品牌表,brand_id、brand_name、logo_url
  • pms_category:分类表,用parent_id实现树形结构
  • pms_sku:商品 SKU 表,存具体规格和库存

订单库gulimall_oms.sql里是oms_order和oms_order_item,用户库gulimall_ums.sql里是ums_member。三库分离的设计意味着你本地要建三个 database,或者至少建一个库但用不同前缀区分表名。

建表语句里有个细节值得注意:pms_category的parent_id默认值是 0,表示顶级分类。查询树形结构时,代码里通常先查parent_id = 0的根节点,再递归查子节点。如果数据量不大,一次性查出所有分类在内存里组装树更高效;数据量大了才考虑递归 SQL 或缓存。

3.2 初始数据 INSERT:没有这些数据页面就是空的

建表之后是INSERT语句,往pms_category和pms_brand里塞了一些初始数据。比如手机数码、家用电器这些一级分类,以及华为、小米、苹果这些品牌名。这些数据是页面能正常显示的前提——如果只建表不插数据,前端调接口返回空数组,页面就是白板一块。

我见过有人只执行了建表部分,跳过了 INSERT,然后跑来问「为什么商品列表是空的」。所以导入 SQL 的时候,建议整个文件从头到尾执行一遍,别只复制建表语句。另外注意字符集,建表语句末尾通常是ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,如果你的 MySQL 版本较老,可能需要改成utf8,否则中文会变问号。

3.3 索引与约束:哪些字段查得最频繁

翻到建表语句的后半段,能看到一些KEY和UNIQUE KEY定义。比如pms_product表上category_id和brand_id通常有普通索引,因为列表页经常按分类或品牌筛选。pms_sku表上product_id有索引,用于查某个商品的所有规格。

这些索引不是随便加的,是跟着查询场景走的。如果你二次开发时要加一个「按价格区间筛选」的功能,那price字段可能也需要加索引。但别一股脑给所有字段都加,写入性能会下降。我一般先用EXPLAIN看一下慢查询的执行计划,再决定加哪个索引。

提示:导入 SQL 前先确认 MySQL 版本,5.7 和 8.0 在字符集和默认排序规则上有差异,可能导致建表失败。

4. 核心模块代码走读:从商品列表接口看请求全链路

4.1 Controller 层:一个 GET 请求怎么被接住

商品列表接口通常长这样:

@RestController @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; // 分页查询商品列表,支持按分类和品牌筛选 @GetMapping("/list") public R list(@RequestParam Map<String, Object> params) { // params 里包含 page、limit、categoryId、brandId 等 PageUtils page = productService.queryPage(params); return R.ok().put("page", page); } }

@RestController让返回值自动转 JSON,@RequestParam Map把 URL 上的查询参数一股脑接进来,避免为每个筛选条件写一个方法参数。R是公共模块里的统一返回类,R.ok()设置code=0,put往data里塞数据。前端拿到{ code: 0, page: { list: [...], totalCount: 100 } }这样的结构,就能渲染表格和分页条。

参数说明:page是当前页码,limit是每页条数,categoryId和brandId是可选的筛选条件。如果前端没传,后端应该给默认值,比如page=1、limit=10,否则分页查询会报错。

4.2 Service 层:MyBatis-Plus 分页与条件构造

Service 实现类里核心是这几行:

@Service("productService") public class ProductServiceImpl extends ServiceImpl<ProductDao, ProductEntity> implements ProductService { @Override public PageUtils queryPage(Map<String, Object> params) { // 构造查询条件 QueryWrapper<ProductEntity> wrapper = new QueryWrapper<>(); String categoryId = (String) params.get("categoryId"); if (StringUtils.isNotBlank(categoryId)) { wrapper.eq("category_id", categoryId); } String brandId = (String) params.get("brandId"); if (StringUtils.isNotBlank(brandId)) { wrapper.eq("brand_id", brandId); } // 按创建时间倒序 wrapper.orderByDesc("create_time"); // 分页查询 IPage<ProductEntity> page = this.page( new Query<ProductEntity>() .setCurrent(Long.parseLong(params.get("page").toString())) .setSize(Long.parseLong(params.get("limit").toString())), wrapper ); return new PageUtils(page); } }

ServiceImpl<ProductDao, ProductEntity>是 MyBatis-Plus 提供的基类,this.page()方法自动处理LIMIT和COUNT查询。QueryWrapper用来拼WHERE条件,eq是等值匹配,orderByDesc是排序。PageUtils把IPage转成前端友好的结构,包含list、totalCount、pageSize、totalPage这几个字段。

这里有个容易翻车的地方:params.get("page")如果前端没传,会返回null,直接.toString()就空指针了。稳妥做法是先判空再给默认值,或者用MapUtils工具类统一处理。

4.3 DAO 层与 XML:复杂查询什么时候必须手写 SQL

单表查询用BaseMapper就够了,但商品列表往往要关联品牌名和分类名。比如前端表格里要显示「品牌:华为」而不是「brand_id: 3」,这就需要 JOIN 查询。对应的 XML 大概是这样:

<select id="selectProductWithBrand" resultType="com.gulimall.product.entity.ProductVO"> SELECT p.product_id, p.product_name, p.price, b.brand_name, c.category_name FROM pms_product p LEFT JOIN pms_brand b ON p.brand_id = b.brand_id LEFT JOIN pms_category c ON p.category_id = c.category_id <where> <if test="categoryId != null"> AND p.category_id = #{categoryId} </if> <if test="brandId != null"> AND p.brand_id = #{brandId} </if> </where> ORDER BY p.create_time DESC </select>

resultType指向一个 VO 类,里面多加了brandName和categoryName两个字段。<where>标签会自动处理第一个AND,<if>做动态条件拼接。这种写法比在 Java 里拼字符串安全得多,也更容易维护。

参数说明:categoryId和brandId是可选参数,不传就不拼对应的AND条件。#{}是预编译占位符,能防 SQL 注入,别用${}拼字符串。

5. 本地跑通与避坑:从零到页面出数据的完整步骤

5.1 环境准备与依赖安装

先把 MySQL 装好,建一个库叫gulimall_pms(或者按 SQL 文件里的CREATE DATABASE语句来)。然后导入 SQL:

mysql -u root -p gulimall_pms < sql/gulimall_pms.sql

接着改application.yml里的数据源配置:

spring: datasource: url: jdbc:mysql://localhost:3306/gulimall_pms?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver

serverTimezone必须设,不然 MySQL 8.0 会报时区错误。characterEncoding=utf-8保证中文不乱码。

5.2 启动顺序与常见报错

如果项目是多模块的,先 install 公共模块:

cd gulimall-common mvn clean install -DskipTests

然后再启动 product 模块。跳过测试是因为测试类可能依赖外部服务,本地跑会失败。

常见报错一:Unable to find a @SpringBootConfiguration。原因是启动类不在根包下,或者@SpringBootApplication没加。检查启动类的位置,确保它在所有 Controller 和 Service 的父包下。

常见报错二:Table 'gulimall_pms.pms_product' doesn't exist。说明 SQL 没导入成功,或者导入到了错误的库。用SHOW TABLES;确认表存在。

5.3 前端页面联调与跨域处理

把html目录下的文件放到 Spring Boot 的src/main/resources/static下,启动后访问http://localhost:8080/index.html。如果页面能打开但数据加载不出来,按 F12 看 Network 面板,大概率是接口地址写死了或者跨域被拦。

跨域问题可以在 Controller 上加@CrossOrigin,或者在配置类里全局配置:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true) .maxAge(3600); } }

allowedOriginPatterns("*")比allowedOrigins("*")更安全,后者在带 Cookie 的请求下会报错。maxAge(3600)表示预检请求缓存一小时,减少 OPTIONS 请求次数。

注意:生产环境别用*放开所有来源,按实际域名配置。

6. 二次开发与排查:改哪里、怎么验证、别踩哪些坑

6.1 常见问题排查清单

现象一:页面能打开但商品列表一直转圈。原因:接口返回了但前端没解析对,或者接口报 500 但被前端吞了。 解决:打开浏览器控制台看 Network,找到/product/list请求,看 Response 里的code是不是 0。如果是 500,看后端日志的异常堆栈。

现象二:分页参数传了但每页还是显示全部数据。原因:MyBatis-Plus 的分页插件没配置,this.page()退化成普通查询。 解决:在配置类里加MybatisPlusInterceptor并注册PaginationInnerInterceptor。

现象三:中文商品名显示成问号。原因:数据库连接串没加characterEncoding=utf-8,或者建表时字符集不对。 解决:改连接串,重建表时指定DEFAULT CHARSET=utf8mb4。

现象四:修改了实体类字段但数据库没同步。原因:MyBatis-Plus 默认不会自动更新表结构。 解决:手动改 SQL 或者用 Flyway 这类迁移工具,别指望 ORM 自动加列。

现象五:多模块下改了 common 模块,product 模块没生效。原因:common 模块没重新 install 到本地仓库。 解决:每次改完 common,执行mvn clean install -DskipTests,再重启 product。

6.2 二次开发切入点:加一个「商品搜索」功能

想练手的话,最自然的切入点是给商品列表加搜索。步骤:

  1. 在pms_product表上加FULLTEXT索引,或者用LIKE查询(数据量小的时候够用)。
  2. Controller 的list方法里多接一个keyword参数。
  3. Service 的QueryWrapper里加.like("product_name", keyword)。
  4. 前端页面加一个搜索框,把输入值拼到请求参数里。
String keyword = (String) params.get("keyword"); if (StringUtils.isNotBlank(keyword)) { wrapper.like("product_name", keyword); }

like生成的是LIKE '%keyword%',前后模糊匹配。数据量上万之后要考虑全文索引或搜索引擎,但练手阶段这样写完全够。

6.3 一个让我长记性的教训

我第一次跑这个项目的时候,图省事只导入了建表语句,跳过了 INSERT。结果页面打开全是空白,我以为是前端代码写错了,折腾了两个小时查 JS 报错,最后才发现是数据库里一条数据都没有。从那以后我每次拿到带 SQL 的项目,都强制走一遍「建库 → 导全量 SQL →SELECT COUNT(*)确认数据行数」的流程,再去看代码。希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询