资讯动态

谷粒商城基础篇Second实战:商品分类、品牌与属性管理全链路解析

发布时间:2026/10/20 1:54:20 来源:尧图企业网站定制
谷粒商城项目的基础篇认真过完两遍之后我发现最有价值的部分其实是Second这个阶段。它不是单纯地往后多做了几个页面而是第一次让我把“一个后台管理系统是怎么从零长出来的”这件事完整看清楚了。基础篇First解决了工程骨架网关、注册中心、公共模块都就位了但项目跑起来之后界面上还是空的。到了基础篇Second才开始真正碰业务——商品分类、品牌管理、属性的基础CRUD再加上前后端联调里那些绕不开的坑。这篇就把我做完后梳理的东西全部分享出来给正在卡在这个阶段的人一个能直接照做的参考。1. 基础篇Second拿到手里先看这是一盘什么棋1.1 为什么一个项目要拆成两个阶段来做很多刚接触的人会疑惑既然是“谷粒商城项目”这样一个完整电商系统为什么要单独分出First和Second而且Second还叫“基础篇”的一部分。我的理解是这样的电商项目的复杂度是渐进式增长的如果在一开始就把秒杀、订单、支付、购物车全塞进来代码还没写两行就会被分布式事务和各种中间件劝退。所以基础篇的策略是先把“后台管理端”这种东西做透再往后才进入真正的C端高并发场景。基础篇Second处于一个承上启下的位置上游已经把工程化和基础设施处理好了下游要开始做具体的商品业务。在这个阶段你接触的不是高并发不是分布式锁而是一个业务系统里最朴素也最重要的事——把一张表变成一组接口把一组接口变成一个能操作的页面。不要觉得这个简单我见过不少做了一两年的人写CRUD接口没问题但让他从零搭一个带权限、带树形结构、带级联操作的后台页面还是会卡住。Second阶段的本质就是用商品分类这个最常见的业务模型把这种全链路能力练熟。1.2 Second阶段那几块硬骨头整个Second阶段如果压缩成一句话就是“把商品后台里最基础的那几个模块做完”。拆开来看大概是三个业务块商品分类、品牌管理、属性管理。这三个模块表面上是三个独立的CRUD实际上互相之间是有关系的。商品分类是典型的无限级树形结构数据要支撑三层以上的父子层级前端要能渲染成树后端要有办法递归查询。品牌管理看起来就是表格加新增编辑但它要和分类建立关联关系还要考虑图片上传这就牵扯到文件存储。属性管理实际上是后面SPU和SKU的前置一组属性要能被分组维护属性组和属性之间的绑定关系要能动态调整。这几个模块做完你会发现自己的脑子里多了一张“电商商品模型”的草图后面学到SKU、库存那些内容时就能把新知识挂在这张草图上。1.3 这个阶段练的是哪几项底层能力说几个我在做Second阶段时反复体会到的能力点。第一是建立表结构的直觉。什么时候该建中间表什么时候该在业务表里加一个冗余字段这些不实践真的很难有体感。Second阶段里分类和品牌的关联、属性和属性组的关联都让我对“关联关系如何落表”有了更具体的认识。第二是前后端联调的系统性思维。本地起前端、起后端、连数据库三个端同时工作任何一个环节环境不对都跑不通。这个排错过程比写代码本身更能锻炼人。第三是学会看日志和定位报错。很多人一开始遇到报错就发聊天截图问别人其实日志里的堆栈信息已经把答案写得很清楚了。Second阶段前面会频繁遇到跨域、路由冲突、数据格式不对这类问题每解决一个你对这套技术栈的理解就会深一层。所以我的建议是不要急着往后刷课程先把这一段吃透。它不炫技但它是一切后续业务展开的地基。2. 数据模型商品分类、品牌、属性这三张表怎么设计2.1 分类表一张无限级分类表搞定三层结构先看商品分类。电商系统里分类通常是三层比如“手机数码”下面有“手机通讯”“手机通讯”下面又有“智能手机”。用数据库表来表达这种层级关系最标准的做法就是给分类表加一个parent_id字段自关联。这个设计很经典我建表时用的大概是这么个结构CREATE TABLE pms_category ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 分类id, name varchar(50) DEFAULT NULL COMMENT 分类名称, parent_id bigint(20) DEFAULT NULL COMMENT 父分类id, cat_level int(11) DEFAULT NULL COMMENT 层级, sort int(11) DEFAULT NULL COMMENT 排序, icon varchar(255) DEFAULT NULL COMMENT 图标地址, status tinyint(4) DEFAULT NULL COMMENT 是否显示, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分类表;其实核心就两列id和parent_id。parent_id为0的就是顶级分类其他情况的上级通过parent_id指向另一条记录的id。我最初踩过的一个小坑是以为还需要一个字段记录完整路径比如“/0/1/2”其实完全没必要树形结构在查询的时候通过递归或者一次性查出来内存里组装就行。有些项目会加一个cat_level来做层级标记Second阶段里这个字段是有的它的作用是避免每次都递归到全表方便做逻辑判断。不过说实话在小数据量下这个字段的优先级不高主要是为了语义清晰。2.2 品牌表与分类的关联中间表的意义品牌和分类之间是多对多关系。一个品牌可以生产手机也可以生产家电一个分类下面可以有多个品牌。这种多对多关系在关系型数据库里最标准的解法是中间表。我当时设计的品牌表主要是这些字段CREATE TABLE pms_brand ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(50) DEFAULT NULL, logo varchar(255) DEFAULT NULL, descript text, show_status tinyint(4) DEFAULT NULL, sort int(11) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT品牌表;然后是一张品牌分类关联表CREATE TABLE pms_category_brand ( id bigint(20) NOT NULL AUTO_INCREMENT, brand_id bigint(20) DEFAULT NULL, category_id bigint(20) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT品牌分类关联表;也许有人会问为什么不直接在品牌表里加category_id字段非要单独建一张关联表因为这是多对多关系一个品牌如果同时属于三个分类就需要三条记录如果只用一列去存要么逗号拼接要么只能存一个都会给后续查询和一致性带来麻烦。所以“多对多必须拆成中间表”这个习惯要一开始就养成。2.3 属性分组与属性值SPU/SKU之前的前置设计属性这块如果没有做过电商刚开始会绕不明白。谷粒商城基础篇Second里关于属性的设计思路很清晰先把属性分到组里再把组和分类绑定。属性分组表和属性表的简单结构是CREATE TABLE pms_attr_group ( id bigint(20) NOT NULL AUTO_INCREMENT, attr_group_name varchar(50) DEFAULT NULL, category_id bigint(20) DEFAULT NULL, sort int(11) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT属性分组表; CREATE TABLE pms_attr ( id bigint(20) NOT NULL AUTO_INCREMENT, attr_name varchar(100) DEFAULT NULL, attr_type tinyint(4) DEFAULT NULL COMMENT 0-销售属性 1-规格属性, value_type tinyint(4) DEFAULT NULL COMMENT 0-单选 1-多选, search_type tinyint(4) DEFAULT NULL COMMENT 是否需要检索, category_id bigint(20) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT属性表;还有一个关联表把属性和属性组绑定起来CREATE TABLE pms_attr_attrgroup_relation ( id bigint(20) NOT NULL AUTO_INCREMENT, attr_id bigint(20) DEFAULT NULL, attr_group_id bigint(20) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT属性与属性组关联表;这里面的理念是属性是通用概念比如“颜色”“尺寸”属性组是把通用概念聚合起来比如“基本信息”“规格参数”。这样设计的好处是后面在做SPU和SKU的时候可以直接根据分类找到对应的属性组再从属性组拿到属性列表属性值就能动态渲染到前端表单里。简而言之Second阶段建的表全都是在为后面更复杂的商品发布做准备。3. 后端核心实现从建表到接口联调3.1 逆向生成代码不要从零手写基础篇Second项目里后端主要基于Spring Boot MyBatis-Plus。如果你选的数据库是MySQL建议直接用MyBatis-Plus代码生成器把实体类、Mapper、Service、Controller一次性生成出来再在生成的基础上改造。不是说自己手写不好而是生成器能保证基础的CRUD代码风格统一尤其适合跟着做项目的人减少重复劳动。最简单的生成方式是用MyBatis-Plus的AutoGenerator配置好数据库连接、包名、模块名它会自动把实体类和基础接口生成好。我当时生成的controller里已经有了基础的增删改查方法但实际业务远不止这些比如分类的树形查询、菜单拖拽后保存顺序、属性分组的级联删除都需要自己再改。所以我的经验是把生成器当“脚手架”用别当最终结果。3.2 CRUD接口里的三个隐藏考点基础篇Second的接口开发表面上只是CRUD但有几个细节值得专门说。第一个是新增和修改要区分处理。比如品牌管理新增的时候不需要传id修改的时候必须有id。很多人直接写一个save方法通过判断id是否为空来分流这没问题。但更规范的是把逻辑拆成两个接口避免前端调错。我当时用的是同一个Post请求通过参数里有没有id来区分这个方法简单也够用。第二个是查询列表时要处理排序值。分类的排序字段sort前端树形组件拖拽排序后会按order传回来后端的保存接口要能批量更新每个分类的sort值。这里不能只更新一条要用批量更新否则拖拽排序后刷新页面顺序又回去了。第三个是级联数据处理。删除一个分类要考虑到它有没有子分类如果有子分类直接删会出问题必须先处理子分类或者禁止删除。删除一个属性组要同步删除属性组和属性的关联记录。这些逻辑不在主业务表里而是在关联表里如果不处理就会留下脏数据。下面贴一段我当时处理属性组删除的简化示例Override public void removeAttrGroupByIds(ListLong ids) { // 批量删除属性组 this.removeByIds(ids); // 清理属性组与属性的关联关系 attrGroupRelationService.remove(new LambdaQueryWrapperAttrAttrgroupRelationEntity() .in(AttrAttrgroupRelationEntity::getAttrGroupId, ids)); }这就是一个很典型的“删除主数据但别忘关联数据”的场景。新手容易漏漏了之后后面查属性组详情时就会发现数据对不上。3.3 分页查询与条件查询后台管理页面几乎离不开分页。MyBatis-Plus里分页写法很统一先注入分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后在Service里用Page对象public PageUtils queryPage(MapString, Object params) { IPageBrandEntity page this.page( new QueryBrandEntity().getPage(params), new LambdaQueryWrapperBrandEntity() .like(!StringUtils.isEmpty(keyword), BrandEntity::getName, keyword) ); return new PageUtils(page); }其中keyword是前端传的搜索关键字通过like条件实现模糊搜索。这个写法的细节在于如果前端没传搜索关键字那就不能拼上like条件否则SQL会变成like %%不仅没意义还可能影响索引。关于分页我再补一个很容易被忽略的点Page对象在数据量少的时候看不出问题但一旦数据量大建议把排序字段明确写在SQL里否则同样的数据每次查出来顺序可能不一致。4. 前端后台管理系统菜单、路由、页面联调4.1 动态路由不等于复杂先理解为什么需要基础篇Second的前端用的是一个开源后台管理模板这类模板一般自带登录、权限、动态菜单框架。很多人一上来就懵搞不清为什么菜单是从数据库里查出来的而不是直接写在router里。其实道理很简单后台管理系统的菜单和权限是绑定的。不同角色的用户登录后应该看到不同的菜单如果前端写死路由那就没法动态控制可见范围。基础篇Second阶段虽然还没做完整权限但已经引入了动态菜单查询的概念前端从后端接口把菜单列表查出来然后动态注册成路由。我个人的建议是第一阶段不要纠结权限模型怎么设计先接受“菜单来源于接口”这个设定。前端拿到数据后用v-for渲染菜单列表点击菜单跳转对应的路由组件联调通了之后后面加减菜单就变成了改数据库的活这个体感比什么都写死爽很多。4.2 树形组件的使用是前端重头戏商品分类管理页面核心交互是树形结构展示。开源模板里一般有现成的树形组件可用。但组件只是壳真正要处理的是数据格式。后端返回的列表如果是平铺的前端需要把它组装成树如果后端已经返回树形结构前端直接用就行。我在做这段时采用了后端返回完整列表、前端用递归组装成树的方案。原因很简单树形数据量不大时前端组装比后端递归更直观也好调试。组装逻辑大概是遍历列表找每个节点的children列表入口条件是parentId为0。组装完成后还有个交互难点点击分类节点时要联动加载当前分类下的品牌列表和属性信息。这意味着要用到组件的事件回调把当前节点id传给后端接口。这个联动的价值在于它让你明白后台不是一个一个孤立的页面而是数据之间有联动关系的系统。4.3 axios封装与跨域前后端分离开发跨域问题迟早遇到。开发环境里的跨域解法很简单配置代理转发。前端工程里一般有个vue.config.js配置devServer.proxy把接口路径代理到后端地址devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } }这样配置后前端请求/api/product/brand/list会被转发到http://localhost:8080/product/brand/list。我最初看到这个配置时没太在意后来发现很多联调问题都出在这要么没配置代理要么pathRewrite写错要么后端接口路径和代理后的路径对不上。axios封装也是这阶段的一个重要工作。统一请求头、统一响应处理、统一错误提示都能在项目里沉淀成一个request.js文件。后期做任何业务模块都直接复用这一套封装。5. 我踩过的几个坑和排查思路5.1 跨域配置和预检请求开发时我遇到的第一个拦路虎就是跨域。前端8080端口访问后端8080端口浏览器默认会拦截。虽然可以用代理解决但如果你直接访问后端地址仍需后端开启跨域支持。后端开启跨域有两种常见写法一是写一个CorsFilter的Bean二是给Controller加CrossOrigin注解。项目里用的通常是全局跨域配置。需要注意的细节是如果使用了网关作为统一入口网关层要透传跨域相关信息否则前端还是会报CORS错误。有个让我排查很久的问题是“跨域请求发送了但没带自定义请求头”。这是因为preflight预检请求没通过。预检请求是浏览器在发送复杂请求之前自动发出的一个OPTIONS请求如果后端处理不了OPTIONS请求真正的请求就不会发送。排查的时候看到响应头里没有Access-Control-Allow-Headers基本就是这个问题。5.2 树形数据更新后不刷新分类拖拽排序后前端树组件显示的顺序和后端存的不一样这个坑也很典型。原因大多数是排序字段没有正确传送到后端或者后端保存接口没有按sort字段排序返回。我的排查思路是先看网络请求体拖拽结束有没有发出请求请求体里边的order值是不是前端拖拽后的顺序。如果请求体没问题那就看后端有没有按sort排序。有些树形组件在回显数据时会默认按id排序导致sort字段没生效。这个问题最后的解决方式是在保存接口里把整棵树的sort值都更新一遍同时在查询接口里明确加排序条件。5.3 模块之间耦合导致启动失败Second阶段随着工程模块增多Spring Boot启动越来越容易出问题。常见的一种是重复扫描了另一模块的Controller或Service导致Bean冲突。排查方式很简单启动日志里会明确告诉你哪个Bean冲突重点去看扫描包的路径是否范围太大。我当时的做法是给每个模块的启动类配置精确的包扫描路径不要用默认的全包扫描。这样虽然多写几行注解但模块多了以后能省掉很多无谓的冲突。5.4 常见问题速查表问题现象可能原因解决思路前端请求跨域报错网关或后端未允许跨域配置全局跨域检查预检请求处理分类树不显示子节点递归组装未识别0为根节点确认parentId默认值检查入参修改后刷新数据被还原保存时未传id走了新增逻辑区分新增和修改检查请求参数属性组删除后残留关联数据只删了主表没删关联表删除逻辑中同步清理关联关系启动时报Bean冲突包扫描范围过大精确指定模块扫描路径图片上传后页面不显示访问路径未映射到存储目录配置静态资源映射或OSS访问路径6. 最后的实操建议和阶段复盘这个阶段做完我个人最大的收获不是会写CRUD而是能把一个数据库表从建模到页面展示完整串起来。做项目的时候很多人喜欢直接跳到后端的复杂技术像是分布式事务、消息队列反而容易忽略这些最基础的“表-接口-页面”链路。但实际工作中大部分日常需求就是围绕着这个链路在转。如果你现在也做到谷粒商城基础篇Second这个位置我有几个实操建议可以参考。第一不要只盯着代码要自己在Navicat或DataGrip里把表建一遍字段类型都敲一遍。建表过程中你会自然去想每个字段的意义这个思考过程比看教程有价值得多。第二每个接口写完先不要急着做前端页面用接口测试工具把入参、出参都测一遍。我在Second阶段学到的一个习惯是前端页面报错时先去看后端接口通不通再去看前端代码对不对这能帮你快速缩小问题范围。第三遇到的每一个报错尽量自己去读日志不要一看到异常就直接去问人。日志里经常藏着关键信息比如“Cannot find column”“ClassNotFoundException”一行一行读下来大部分问题都能定位。如果实在找不到答案再把报错信息完整复制出来搜索而不是只发一个“报错了”的截图。第四把这一步做完之后可以试着自己做个小扩展比如给商品分类加一个“是否导航栏展示”的开关前端再加一个Tab页展示。不用改动核心架构但能逼着你把整个流程重新走一遍。这一遍做完基础篇Second里学到的东西才会真正沉淀下来。最后再说一个小技巧做这类多模块项目一定不要在同一个idea窗口里同时开七八个微服务模块机器很容易卡死。按需要启动比如做商品分类就只启动网关、商品服务、前端工程其他模块暂时关掉能节约大量等待时间。做完这一步再去开启新的模块效率会明显不一样。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑