YAOTU INSIGHTS

谷粒商城全栈项目实战:Spring Boot + MyBatis-Plus 电商系统快速搭建与避坑指南

谷粒商城全栈项目实战:Spring Boot + MyBatis-Plus 电商系统快速搭建与避坑指南
简介这份资源是谷粒商城项目的完整技术压缩包面向正在学习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 不用写 SQLMySQL5.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_urlpms_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 的时候建议整个文件从头到尾执行一遍别只复制建表语句。另外注意字符集建表语句末尾通常是ENGINEInnoDB DEFAULT CHARSETutf8mb4如果你的 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 MapString, Object params) { // params 里包含 page、limit、categoryId、brandId 等 PageUtils page productService.queryPage(params); return R.ok().put(page, page); } }RestController让返回值自动转 JSONRequestParam Map把 URL 上的查询参数一股脑接进来避免为每个筛选条件写一个方法参数。R是公共模块里的统一返回类R.ok()设置code0put往data里塞数据。前端拿到{ code: 0, page: { list: [...], totalCount: 100 } }这样的结构就能渲染表格和分页条。参数说明page是当前页码limit是每页条数categoryId和brandId是可选的筛选条件。如果前端没传后端应该给默认值比如page1、limit10否则分页查询会报错。4.2 Service 层MyBatis-Plus 分页与条件构造Service 实现类里核心是这几行Service(productService) public class ProductServiceImpl extends ServiceImplProductDao, ProductEntity implements ProductService { Override public PageUtils queryPage(MapString, Object params) { // 构造查询条件 QueryWrapperProductEntity 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); // 分页查询 IPageProductEntity page this.page( new QueryProductEntity() .setCurrent(Long.parseLong(params.get(page).toString())) .setSize(Long.parseLong(params.get(limit).toString())), wrapper ); return new PageUtils(page); } }ServiceImplProductDao, 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 idselectProductWithBrand resultTypecom.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 testcategoryId ! null AND p.category_id #{categoryId} /if if testbrandId ! null AND p.brand_id #{brandId} /if /where ORDER BY p.create_time DESC /selectresultType指向一个 VO 类里面多加了brandName和categoryName两个字段。where标签会自动处理第一个ANDif做动态条件拼接。这种写法比在 Java 里拼字符串安全得多也更容易维护。参数说明categoryId和brandId是可选参数不传就不拼对应的AND条件。#{}是预编译占位符能防 SQL 注入别用${}拼字符串。5. 本地跑通与避坑从零到页面出数据的完整步骤5.1 环境准备与依赖安装先把 MySQL 装好建一个库叫gulimall_pms或者按 SQL 文件里的CREATE DATABASE语句来。然后导入 SQLmysql -u root -p gulimall_pms sql/gulimall_pms.sql接着改application.yml里的数据源配置spring: datasource: url: jdbc:mysql://localhost:3306/gulimall_pms?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.DriverserverTimezone必须设不然 MySQL 8.0 会报时区错误。characterEncodingutf-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 doesnt 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。现象三中文商品名显示成问号。原因数据库连接串没加characterEncodingutf-8或者建表时字符集不对。 解决改连接串重建表时指定DEFAULT CHARSETutf8mb4。现象四修改了实体类字段但数据库没同步。原因MyBatis-Plus 默认不会自动更新表结构。 解决手动改 SQL 或者用 Flyway 这类迁移工具别指望 ORM 自动加列。现象五多模块下改了 common 模块product 模块没生效。原因common 模块没重新 install 到本地仓库。 解决每次改完 common执行mvn clean install -DskipTests再重启 product。6.2 二次开发切入点加一个「商品搜索」功能想练手的话最自然的切入点是给商品列表加搜索。步骤在pms_product表上加FULLTEXT索引或者用LIKE查询数据量小的时候够用。Controller 的list方法里多接一个keyword参数。Service 的QueryWrapper里加.like(product_name, keyword)。前端页面加一个搜索框把输入值拼到请求参数里。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(*)确认数据行数」的流程再去看代码。希望帮到你。本文还有配套的精品资源点击获取