资讯动态

基于SpringBoot的家具销售管理系统完整复盘

发布时间:2026/10/16 3:53:20 来源:尧图企业网站定制
看到基于SpringBoot的家具销售管理系统这个标题的时候我的第一反应不是赶紧去找源码而是先想一个问题这类系统到底怎么拆才值得真正写进简历和答辩PPT里。家具销售管理系统听起来像个普通电商但实际上它的业务比标准电商要重得多——大件商品、规格复杂、库存敏感、订单履约链路长。这篇文章我会把整套系统的需求拆解、数据库设计、核心功能实现、部署踩坑和延伸思路完整复盘一遍适合正在准备毕设、刚学完Spring Boot想做实战项目、以及想理解一个中小型Web系统从0到1怎么搭建的开发者参考。我不绕弯子直接开拆。1. 项目定位与核心需求拆解1.1 家具销售系统到底在解决什么问题先聊业务。家具销售和普通快消品电商有一个明显区别普通商品SKU简单买一件T恤选个尺码颜色就结束了家具不一样一张餐桌可能有实木、岩板、人造石几种台面一套沙发要分尺寸、填充物、面料颜色、送货区域。如果业务早期只靠Excel管库存客户打电话来问有没有货、什么价、送到哪个城市每次都要翻表几个人同时改同一份表格还会互相覆盖。这种场景催生了管理系统的需求。家具行业真正的痛点集中在三块商品信息乱、库存账实不符、订单售后难追溯。所以这个系统设计时至少要覆盖三条线用户端负责商品展示、搜索、购物车和下单管理端负责商品上架下架、分类维护、库存调整订单模块负责从创建、支付、发货到确认收货的全流程跟踪。商品展示解决用户能看到什么下单解决怎么把商品卖出去后台订单解决卖出去之后怎么管。这三条线走通系统的骨架就立住了。1.2 用户角色与核心业务流程系统一般拆成前台和后台两个入口。前台用户能做的操作比较常规注册登录、浏览商品、按分类筛选、关键词搜索、查看商品详情、加入购物车、结算下单、模拟支付、查看订单状态、确认收货、维护个人资料。后台管理员则负责另一套操作管理商品分类、发布或下架商品、修改库存、处理订单发货、取消订单、管理首页轮播图和公告。权限上通常用一个字段区分身份0代表普通用户1代表管理员登录后根据角色决定跳转到用户首页还是后台管理页。这里有个设计细节容易被新手忽略用户端和管理端的接口不能混在同一个Controller里。有人图省事一个ProductController既给前端商品列表用又给后台新增商品用结果越权问题很难查。更合理的做法是用户端走UserProductController后台管理走AdminProductController各自做参数校验和权限控制。核心业务闭环是这样的注册登录 → 分类浏览或搜索 → 加入购物车 → 修改数量 → 结算 → 生成订单 → 模拟支付 → 管理员发货 → 用户确认收货。这一条流程里最容易出错的就是结算环节很多新手直接把前端购物车里的商品数据传后端前端传什么就下什么。正确做法是前端只传购物车记录的id集合后端根据id重新从数据库查出商品信息和实时价格重新算总价。为什么必须这样因为金额和库存必须以数据库为准前端页面里的价格是可以被改掉的这不是危言耸听是真实项目里的基本底线。标题里带了源码两个字很多同学下载完源码就急着跑起来看页面界面一亮就觉得项目学会了。实际上这套系统真正值钱的地方恰恰不是页面而是前端勾选购物车到后端生成订单这段数据流。能把这个数据流讲清楚面试和答辩的时候就已经赢了大部分人。1.3 技术选型背后的理由Spring Boot在课程设计和毕业设计里出现频率这么高不是因为赶时髦而是因为它真的省事。对比老一代SSM项目Spring Boot把配置文件简化了一大截内嵌Tomcat不用再部署WAR包到外部服务器starter机制让依赖引入变得直观想用MyBatis就加一个mybatis-starter想用模板引擎就加一个thymeleaf-starter。很多人在开始前纠结要不要做前后端分离要不要上Vue我的建议是看场景。如果是单人开发或者毕设项目服务端渲染Thymeleaf效率高得多。Controller直接返回一个页面路由由后端控制数据传递链路短不用考虑跨域、Token刷新、接口请求拦截这些问题。前后端分离听起来高级但团队协作和部署复杂度都会上升。如果你面试想展示能力可以在这个项目基础上额外开发一套JSON接口版本但核心表结构完全不用变这个后面我会单独说。2. 数据库设计家具销售系统的表结构与关联关系2.1 整体表清单与设计原则数据库是这套系统的地基我见过不少项目代码写得稀碎但表结构设计得反而很有参考价值。基于SpringBoot的家具销售管理系统一般最少需要这8张表表名作用t_user用户表存账号、密码、角色、昵称t_category商品分类表支持一级或二级分类t_product商品表存价格、库存、图片、详情t_cart购物车表关联用户和商品t_order订单主表存订单状态、总金额、收货信息t_order_item订单明细表存订单中每一件商品的快照t_address收货地址表一个用户可维护多个地址t_banner首页轮播图表非核心但很常见设计原则我认为有三条最重要。第一所有业务表都要有主键id、create_time创建时间、update_time更新时间。不要觉得这个多余真到了线上排查数据问题的时候没有时间的表会让人抓狂。第二不要强行建物理外键。很多教材把外键说得特别重要但实际项目中外键约束常让删除和修改操作变得很僵化MyBatis-Plus这类ORM框架也不鼓励物理外键。外键关系由Service层代码去保证这才是主流做法。第三金额字段用decimal库存用int状态用tinyint这些基础规范会在后续开发中省掉大量调bug的时间。2.2 核心表字段设计详解商品表是信息量最大的表。核心字段包括id主键、category_id分类id、name商品名称、subtitle副标题、main_image主图地址、detail图文详情、price售价、original_price原价、stock库存、sales销量、status上下架状态、deleted逻辑删除标记。价格字段必须用decimal(10,2)不要用float或double因为浮点数在数据库和Java中都会有精度损耗用户付钱的时候一分钱都不能差。decimal(10,2)表示总长度10位、小数点后2位最大能存9999999.99家具单价完全够用。订单部分一定要拆成主表和明细表。t_order存订单整体信息order_no订单号、user_id下单用户、total_amount总金额、status订单状态、receiver_name收货人、receiver_phone手机号、receiver_address详细地址、remark用户备注。为什么还要一张t_order_item因为一个订单可以包含多个商品如果只在一张表里表达要么把多个商品塞进一个字段要么把订单信息重复多行都是灾难。订单明细表存的是下单那一刻的商品快照product_id商品id、product_name商品名称、product_image商品图片、price成交单价、quantity购买数量、subtotal小计金额。这里有个关键点订单明细表需要冗余商品名称、图片和价格而不是订单创建后实时去查商品表。原因很简单商品可能改价、改名、下架甚至删除用户查看历史订单时必须看到的是购买当时的信息。很多二手代码没做这一步被问到订单里的商品价格和现在的价格不一样怎么办就直接卡壳这个点其实是整个项目设计水平的分水岭。2.3 索引设计与其他细节索引设计直接决定查询速度。用户表给username建唯一索引保证注册账号不重复订单表给order_no建唯一索引防止订单号生成重复给user_id建普通索引因为我的订单是用户高频入口给status建普通索引因为管理员经常按待发货、已发货来筛选订单。商品表给category_id和status建联合索引用户进入某个分类时直接过滤上架商品速度快很多。分类表建议用pid字段表达层级关系一级分类pid为0二级分类pid指向一级分类id这样既能做简单的单级分类也能扩展到二级导航。收货地址表加一个is_default字段0为非默认、1为默认下单时可以直接读取默认地址不需要每次手动填写。首页轮播表存图片地址和跳转链接后台维护前台读取字段就id、image、target_url、sort、status五个足够用。关于逻辑删除还有一个小坑要提前提醒如果某个字段建了唯一索引又开了逻辑删除逻辑删除之后再次插入相同值会撞唯一索引。项目里涉及用户名、订单号这类唯一值时要特别小心通常解决办法是唯一索引不建在单字段上或者把删除标记并入联合唯一索引。这个在排错章节我还会展开说。3. 核心功能实现与关键代码解析3.1 环境搭建与项目结构推荐环境组合JDK 1.8、Maven 3.6以上、MySQL 5.7或8.0、Spring Boot 2.7.x、MyBatis-Plus 3.5.x、Thymeleaf 3.x、Lombok。版本不要追新Spring Boot 2.7还在维护期内资料多、兼容稳课程设计项目追求的是稳定和可控不是技术版本号漂亮。包结构建议这样分entity 实体类mapper 数据访问接口service 业务接口service.impl 业务实现类controller 控制层vo 视图对象common 通用类包括响应体、异常、常量config 配置类包括拦截器、分页插件分层的核心意义是让每个类职责单一。Controller只做参数接收和响应返回不写业务规则Service负责事务和核心逻辑Mapper只做SQL交互。这样以后加缓存、改表结构、换前端模板都只动其中一个层次不影响其他部分。很多二手项目最大的问题就是把业务代码全堆在Controller里一个方法几百行看着能跑改起来想哭。3.2 登录认证与密码安全用户密码绝对不能明文存库。加密我推荐直接用BCryptPasswordEncoder这不是额外引入Spring Security全家桶只是单独使用密码编码器依赖轻、用法简单。注册时对明文密码加密再入库登录时把用户输入的明文和数据库中的hash做matches匹配。这两个方法一个是encode一个是matches不要混。登录成功后的会话管理服务端渲染场景下优先选择Session配合拦截器实现统一登录校验。拦截器实现HandlerInterceptor接口preHandle方法里从当前会话拿用户信息拿不到就重定向到登录页。关键代码可以参考public User login(String username, String password) { LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getUsername, username); User user userMapper.selectOne(wrapper); if (user null) { throw new ServiceException(用户名不存在); } if (!passwordEncoder.matches(password, user.getPassword())) { throw new ServiceException(密码错误); } return user; }这里有一个小细节selectOne如果查出来多条记录会直接抛异常所以username字段必须建唯一索引。另外登录成功后不要只存用户名最好把用户id、昵称、角色一起放进Session因为后续购物车、订单操作都需要知道当前用户是谁。3.3 商品分页与条件查询商品列表是访问量最高的页面一口气把全表几百上千条数据查出来渲染是不现实的。用MyBatis-Plus分页需要先注册一个分页插件到容器里Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }不注册这个插件在Mapper里写Page参数不会生效翻页会变成全量查询这是新手项目里出现频率极高的问题。分页查询的代码这样写public IPageProduct pageProducts(int pageNum, int pageSize, ProductQuery query) { PageProduct page new Page(pageNum, pageSize); LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.eq(Product::getStatus, 1); wrapper.eq(query.getCategoryId() ! null, Product::getCategoryId, query.getCategoryId()); wrapper.like(StringUtils.hasText(query.getKeyword()), Product::getName, query.getKeyword()); wrapper.orderByDesc(Product::getCreateTime); return productMapper.selectPage(page, wrapper); }eq方法第一个参数传一个布尔表达式条件满足才拼接SQL。比如用户没有选择分类category null时查询条件自动跳过。这种写法避免了大量if (query ! null)的代码块。like查询会对特殊字符做转义不会因为用户在搜索框输入单引号就把SQL带崩这就是为什么这类项目用QueryWrapper而不是手写字符串拼接SQL的原因。3.4 购物车与下单事务控制购物车表设计越简单越好id、user_id、product_id、quantity、checked、create_time、update_time。加入购物车的逻辑是先查这个用户是否已经加了同一商品有就数量加1没有就插入新记录。修改数量时后端要重新校验库存上限不能让用户把数量改到超过可用库存还能提交。下单是整个系统里含金量最高的代码。完整流程如下接收前端传过来的购物车id列表根据id查出购物车记录再批量查出关联商品校验商品是否上架、库存是否足够按数据库实时价格计算总金额生成唯一订单号插入订单主表批量插入订单明细扣减商品库存删除对应购物车记录。这八步任何一步失败其他步骤都要回滚否则会出现订单建好了库存没扣或者库存扣了订单没生成的问题。此时直接在方法上标注Transactional让Spring管理事务边界Transactional(rollbackFor Exception.class) public OrderVO createOrder(OrderCreateRequest request) { // 1. 查询购物车 // 2. 校验商品状态与库存 // 3. 计算金额 // 4. 插入订单主表 // 5. 批量插入订单明细 // 6. 扣减库存 // 7. 清理购物车 return orderVO; }库存扣减的SQL特别容易写成先查再减这种写法在并发情况下一定会超卖。两个用户同时买最后一件商品都查到库存是1都执行减1结果两个订单都成功了。正确的扣库存方式是用条件更新Update(UPDATE t_product SET stock stock - #{count}, sales sales #{count} WHERE id #{productId} AND stock #{count}) int reduceStock(Long productId, Integer count);执行结果影响行数为0说明库存不够直接抛异常让事务回滚。这种基于条件更新的做法不需要显式加锁性能损耗也低是教科书级别的防超卖手段。3.5 订单状态流转与模拟支付订单状态建议用常量类统一管理不要到处散落魔法数字。常见的状态机定义1待付款、2已付款、3已发货、4已完成、5已取消。正常流转路径是1到2到3到4。如果要设计取消订单待付款状态可以取消已付款状态下用户申请取消需要管理员介入这个复杂度可以按实际需求取舍。模拟支付是毕设项目的常规操作不会真的去接支付宝微信。订单详情页放一个立即支付按钮点击后调支付接口后端做几件重要的事校验订单归属人是不是当前登录用户检查订单状态必须是待付款然后更新状态为已付款顺便记录支付时间。这里最容易出的越权漏洞是直接按订单id更新不校验归属导致任意登录用户可以支付别人的订单。实现时务必把user_id作为更新条件拼进SQL确保只能操作自己的订单。4. 部署运行与常见问题排查实录4.1 本地快速跑起来拿到源码后第一步是启动这步卡住的人很多。标准流程是先装好JDK 1.8和Maven再装MySQL并新建一个数据库比如取名furniture_sale然后导入项目SQL脚本。打开application.yml修改数据库地址、账号、密码server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/furniture_sale?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver数据库URL里serverTimezone这个参数特别容易被忽略MySQL 8.0版本如果不指定时区启动时经常报Communications link failure或者时区错误。确认无误后在项目根目录执行mvn spring-boot:run浏览器访问http://localhost:8080能看到登录页说明基础环境没问题。如果项目带了初始化页面可能需要先通过注册入口创建账号再用管理员账号进后台具体账号密码看项目加载数据脚本通常会在SQL注释里写。4.2 高频报错排查表我把这类项目最常见的启动和运行问题整理成了一张表排查时直接对照报错现象可能原因解决方式Access denied for user数据库账号密码错误或账号没有远程权限核对application.yml里的账号密码确认root密码正确Communications link failure数据库没启动、端口不对、时区参数缺失确认MySQL运行中URL加上serverTimezoneAsia/ShanghaiTable doesnt existSQL脚本没执行或执行了错误的库确认连接的是furniture_sale库重新导入SQLUnknown column deleted实体里配置了逻辑删除但表里没有deleted字段给表加deleted字段或去掉实体逻辑删除配置Invalid bound statementMapper接口和XML映射文件不匹配检查Mapper接口方法名与XML的id是否一致namespace是否正确ClassNotFoundExceptionMaven依赖冲突或依赖缺失执行mvn clean install重新拉依赖看错误堆栈定位包名分页无效全量返回分页插件没注册在Config类中注入MybatisPlusInterceptor并添加分页插件图片上传后访问404上传目录没有映射为静态资源配置资源映射把本地磁盘目录映射到 /upload/** 路径页面中文乱码数据库字符集或连接字符集不对数据库表用utf8mb4URL加上characterEncodingutf8端口被占用其他程序占用了8080换端口或查找占用进程结束掉这里面有几个问题在二手源码里非常典型分页插件没配导致列表页数据全部平铺、逻辑删除字段缺失导致一切查询都报错、上传图片目录没有做静态资源映射导致图片裂开。每个都算不上难但都能让人折腾一晚上。4.3 服务器部署与上线配置本地跑通之后部署到服务器流程也不复杂。先在项目根目录执行mvn clean package -DskipTests在target目录下会生成一个jar包上传到服务器执行nohup java -jar furniture-sale-1.0.0.jar app.log 21 这样应用就在后台运行了日志输出到app.log。如果需要用80端口对外提供服务更合理的做法是前面放一个Nginx做反向代理由Nginx监听80端口把请求转发到后端的8080。同时Nginx还可以托管前端静态资源和图片上传目录server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }生产环境有几个细节建议直接做数据库密码不要明文写在配置文件里可以用环境变量引用或者引入配置加密组件关闭Spring Boot默认的devtools热重启定期备份数据库。虽然这只是个课程设计或小型项目但安全习惯早养成后面做正式工作不吃亏。5. 从源码到能力的延伸思考5.1 源码到手该怎么用我见过太多人下载了一堆源码最后每个项目都在电脑里吃灰。源码不是用来收藏的而是用来对照学习的。拿到这个家具销售系统后正确的学习顺序应该是先看数据库SQL脚本理解表结构和关系再跑通项目把注册、登录、浏览、下单的完整操作走一遍然后找到订单创建的Service实现一步步跟踪一个订单的生命周期最后尝试从零手写其中一个模块比如购物车或者订单列表。不要只停留在跑起来了。跑起来和能改之间隔着一整个理解的距离。建议至少手写一遍核心的下单逻辑包括购物车查询、库存校验、订单生成、库存扣减、购物车清理这一条链路写完你会比直接看十遍代码收获更大。5.2 改造方向如何把这个项目讲出新意课程设计想拿高分或者简历上想有点亮点可以在现有基础上做几个低成本高展示度的改进。第一个方向是多级分类把现有的单级分类扩展成二级分类分类表加pid字段后台支持树形展示前台首页支持二级菜单。第二个方向是订单取消和库存回补待付款订单可以取消取消时把库存加回来这里能顺带复习事务和状态机的设计。第三个方向是数据统计给管理员后台加一个简单的销售统计页面按日期统计订单数、销售额、热销商品Top10用一条SQL加一个图表库就能实现。如果想挑战高难度可以把服务端渲染改造成前后端分离后端写一套REST接口返回JSON数据前端用Vue或React重写页面。改造过程中能遇到跨域、Token鉴权、接口文档、分页参数适配这些真实开发问题。表格结构基本不用动核心Service可以复用这个改造足够写成一篇文章了。5.3 容易被忽略的安全问题与答辩细节安全方面不要只做登录拦截就算完。我建议至少检查三处第一所有后端接口要校验当前登录用户特别是订单查询、购物车操作防止水平越权第二用户输入的内容无论是搜索关键词还是个人简介都要在页面输出时做转义防止XSS脚本执行第三上传图片要校验文件类型和后缀不能允许用户传.jsp或者.exe文件防止上传木马。答辩时讲这个项目的逻辑顺序很关键不要上来就讲用了什么技术。正确的叙事方式是先讲业务背景说明家具销售有哪些痛点再讲系统的整体设计画一张角色和模块划分图然后讲数据库表设计重点解释为什么要订单主表和明细表拆分最后挑一个技术难点深入讲比如下单过程中的防超卖、事务控制、越权防护。把这个逻辑串下来即使代码不是完美无缺也能让评委觉得你真正理解了系统而不是仅仅下载了一套源码。6. 最后说几句实在话这个项目我前前后后帮人看过不少从源码结构到答辩话术都梳理过。最大的体会是真正让一个项目成型的东西从来不是某个高深的框架而是你把业务逻辑理清、把表结构设计对、把关键流程想透的过程。库存扣减的SQL、订单事务的范围、权限校验的边界这些细节才是你可以写进简历、面试里敢展开讲的内容。如果你手头正好有一套类似的源码建议不要只停留在解压和运行。花一个晚上把数据库脚本逐个表过一遍再花一个下午把订单生成到支付完成的代码走读一遍然后关掉源码自己试着写一个下单接口。卡住了再看看懂了再写。这个过程走完你的收获会和单纯收藏源码天差地别。

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

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

免费获取报价 →
↑