尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring Boot家具销售系统:设计、实现与部署避坑指南

Spring Boot家具销售系统:设计、实现与部署避坑指南 简介基于Web的网上家具用品销售系统设计与实现是一份Java方向的本科毕业设计论文面向计算机专业学生、毕业设计者及对JSP/Java/MySQL开发感兴趣的技术爱好者用于解决家具商城从需求分析、技术选型到功能落地的完整设计问题。整个资源共1个文件为单个doc文档压缩包大小1.66MB内容涵盖系统背景、技术选型以及个人中心、轮播管理、家具资讯、客户审核、家具分类、家具审核、卖家审核、售后维权、统计中心等核心模块设计细节。相比纯代码这份论文更强调软件工程方法与项目文档规范读者能掌握将业务需求转化为系统功能架构的思路学习JSP前后端交互与MySQL数据管理方式并迁移到其他电商或管理系统开发中。目前该资源已有51人学习浏览适合需要快速搭建毕业设计论文框架、了解网上销售系统全貌的在校大学生。1. 基于web的家具销售系统先定业务边界再谈技术实现很多人拿到「基于web的网上家具用品销售系统的设计与实现」这个题目第一反应是开个Spring Boot项目然后狂写CRUD。真这么做你的系统要么在答辩时被评委一句话问住要么在演示时因为某个角落的bug直接翻车。这个标题背后的本质是一套B/S架构的小型电商系统核心不在于「家具」这两个字而在于「销售系统」四个字——它需要覆盖商品展示、检索、购物车、订单生成、库存扣减这条完整的交易链路同时还要兼顾后台管理用的商品维护和订单处理。适合做这个题目的是正在准备毕业设计、课程设计或者想用一套可演示的案例去求职的开发者。本文会按我在实际小项目里验证过的路线从工程选型、数据库设计、核心模块实现到部署答辩把每一步的参数和边界讲清楚让你照着能跑通而不是停留在「能启动就行」。2. 选型与工程骨架单体会话方案比前后端分离更适合这个场景2.1 为什么Spring Boot Thymeleaf是稳妥选择网上家具销售系统的规模决定了技术栈不能太重。常见做法是用Spring Boot做服务端模板引擎用Thymeleaf数据库用MySQL前端样式用Bootstrap或者原生CSS配合少量JavaScript。这种单体架构的好处是你只需要维护一个工程页面由服务端渲染session天然可用不需要处理跨域也不需要在部署时同时起Node服务和Java服务。对毕设或者中小型演示项目来说前后端分离引入的Vue和API调用会让人花大量时间在联调上而收益在这个业务规模下并不明显。如果你在「idea2024版本创建web项目」里看到过Spring Initializr的界面那这条路线对你来说尤其顺手。Spring Initializr生成的项目结构自带启动类和测试骨架你只要在此基础上补业务代码就行。依赖选择上我一般会勾选Spring Web、Thymeleaf、Spring Data JPA或者MyBatis、MySQL Driver、Validation再加一个Lombok减少冗余代码。不要一开始就引入Spring Cloud那套东西销售系统用不到分布式加上去只会让你的启动时间变长、排错范围变大。2.2 用IntelliJ IDEA创建工程的三个参数要填对我用IDEA创建这类工程时有几个地方容易踩坑。Group和Artifact建议用com.furniture和sales-system这种见名知意的组合Java版本选8还是11取决于你本机的JDK选错会导致编译报错Spring Boot版本不要追求最新选2.7.x这类的稳定版本即可因为3.x带来的Jakarta命名空间变化会让你在网上搜到的旧方案对不上。工程创建完成后application.yml是第一个要动的地方。数据源配置里的时区、编码和连接参数直接决定你后面会不会被中文乱码和时差折磨server: port: 8080 servlet: context-path: /furniture spring: datasource: url: jdbc:mysql://localhost:3306/furniture_sales?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowMultiQueriestrue username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false mode: HTML encoding: UTF-8 servlet: multipart: max-file-size: 5MB max-request-size: 20MB mybatis: mapper-locations: classpath:/mapper/*.xml configuration: map-underscore-to-camel-case: true这段配置里characterEncodingutf8和serverTimezoneAsia/Shanghai是我后面排错时最后悔没提前写上的两项。前者不写页面上提交的中文用户名存进数据库就变成问号后者不写订单时间会比北京时间少八个小时答辩时演示「查看近一周订单」会让评委立刻看出问题。allowMultiQueriestrue在MyBatis批量更新库存时会用到注意这是MySQL连接串上的参数不要写在MyBatis配置里。context-path统一加前缀可以避免部署到Tomcat根路径时和你本机其他项目冲突。提示Thymeleaf的cache: false只在开发阶段开部署到生产环境前记得改成true否则每次改完页面模板都必须重启服务才能看到效果演示现场会很尴尬。2.3 分包结构与前端资源的位置工程骨架建好后我习惯按controller、service、mapper或者dao、entity、dto、config、interceptor分包。家具系统业务虽然不复杂但商品、用户、购物车、订单、管理员这五块逻辑相互依赖如果不分层最后改一个需求会牵动十几个类。前端资源放进src/main/resources/static模板页面放进src/main/resources/templatesThymeleaf默认从这里解析。注意静态资源里不要放JSP文件的旧习惯Thymeleaf模板和JSP不能混用混用会导致解析器互相抢资源报错信息又晦涩难懂。3. 数据库设计家具商品表到订单表的状态流转3.1 五张核心表的字段与关系网上家具销售系统的数据库设计我建议按「用户—商品—购物车—订单—订单明细」五张表起步外加一张管理员表。家具商品的特殊点在规格属性多——沙发有材质、尺寸、颜色床有软硬、尺寸、材质。如果在商品表里硬生生加上一堆列后续扩展会非常痛苦。常见做法是主表存通用字段规格差异用spec字段存JSON字符串。MySQL 5.7版本以上支持JSON类型这比拆一张规格表省去很多join操作对演示和简单后台管理都更方便。建表脚本是这类项目里最重要的交付物评委会直接看数据表设计。我把商品表和订单表的关键结构写出来CREATE TABLE product ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(120) NOT NULL COMMENT 商品名称, category_id bigint(20) NOT NULL, price decimal(10,2) NOT NULL COMMENT 现价, original_price decimal(10,2) DEFAULT NULL COMMENT 原价用于展示折扣, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存数量, main_image varchar(255) DEFAULT NULL COMMENT 主图路径, spec json DEFAULT NULL COMMENT 规格材质、尺寸、颜色等, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 1上架 0下架, sales_count int(11) NOT NULL DEFAULT 0 COMMENT 销量排序用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_info ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 业务订单号展示给用户, user_id bigint(20) NOT NULL, total_amount decimal(10,2) NOT NULL, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 0待付款 1待发货 2待收货 3已完成 4已取消, receiver_name varchar(50) NOT NULL, receiver_phone varchar(20) NOT NULL, receiver_address varchar(255) NOT NULL, pay_time datetime DEFAULT NULL, deliver_time datetime DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;order_no用业务订单号而不是自增id作为对外展示的单号我会在第五章的排查部分专门说为什么要这么做。sales_count这个字段很多人忽略但对家具这种低频消费品用户排序时最关注的就是销量没有这个字段就只能用count(订单明细)去实时统计数据量稍大页面就变慢。spec用JSON后商品列表页不需要解析详情页直接转成Map渲染成规格表即可。3.2 购物车与订单明细为什么购物车不存冗余价的实现方案购物车表只需要user_id、product_id、quantity、checked四个字段加一个id。绝对不要在购物车里冗余price字段因为购物车展示的是实时价加入购物车时存了价格结算时后台还要重新查一次商品表校验留着一个过期价格只会带来不一致的bug。订单明细表要冗余下单时的快照信息包括商品名称、单价、图片、规格描述。这属于有意的反规范化设计——用户下单后商品可能改价甚至下架但订单详情必须忠实还原用户付款时看到的商品信息不能跟着实时数据一起变动。订单明细表的order_id关联订单主表在MyBatis里用一个ListOrderItem装载这样在页面端可以用一次查询展示订单和商品列表不会产生N1查询问题。3.3 库存扣减与「下架但未删数据」的约定商品表里我加了status字段下架用0表示而不是物理删除。这个约定在电商后台管理里很重要因为下架商品仍然关联历史订单物理删掉会让订单明细失去参照完整性。后台管理员重置分类、整理页面时下架商品还能帮你保留价格和规格信息。分类表可以再加一个sort_order字段控制展示顺序家具的客厅、卧室、书房这些分类排序直接影响首页的商品陈列效果静态排序比按名称排序靠谱得多。4. 核心功能实现商品检索、购物车与下单扣库存4.1 首页商品列表与多条件检索的实现家具销售系统的检索条件通常是分类价格区间关键词再加一个排序选项。实现时不要指望一条复杂的SQL解决所有情况我更习惯在Service层组装查询条件用MyBatis的动态SQL处理。价格区间用price_min和price_max两个参数接收前端不传就设成nullSQL端用lt;ifgt;判断拼接。关键字搜索用name LIKE CONCAT(%, #{keyword}, %)注意CONCAT在MySQL的索引利用上有限制但这个数据量级下完全够用。商品列表接口返回后前端用Thymeleaf的th:each循环渲染卡片。家具商品的图片一般比较大加载慢是常见问题。我习惯在列表页请求缩略图路径详情页用原图这样列表页响应快不至于因为一次加载二十多张大图把本机浏览器卡住。缩略图可以靠Java端简单缩放上传的图片生成用Thumbnailator这个库几十行代码就能跑通。如果商品图片存在OSS或云存储缩略图和原图分离是基本功如果是本地存储我会在文件上传时就按尺寸各存一份不要偷懒只存原图。4.2 购物车模块登录拦截与数量校验购物车的操作分「加入购物车」「调整数量」「选中结算」三段每一段都有一个隐藏的防御逻辑。数量校验是很多人不写的漏洞——前端页面把输入框的max属性设成库存数但攻击者可以用伪造请求把数量改成999。后端必须在加入购物车和修改数量时重新读取数据库里的库存值如果请求数量大于库存直接拒绝。演示时你可以现场打开浏览器开发者工具改数据证明这个校验是真实存在的。在我实现的项目里购物车接口统一要求登录否则跳转到登录页。这个策略用Spring拦截器实现在WebMvcConfigurer里注册Interceptor放行登录接口、商品列表接口和静态资源路径其余购物车和订单接口全部拦截。注意Session里保存的是用户主键而不是用户对象后续如果修改了用户状态旧session不会立刻同步但这在毕设场景可以接受不必为它引入Redis刷新机制。4.3 下单与扣库存用事务和行锁解决超卖下单是整个系统里最核心、也是评委最爱追问的事务场景。我采用的做法是创建订单、创建订单明细、扣减库存、清空购物车这四步放到同一个事务方法里在扣减库存这一条SQL上使用行级锁。Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateDTO dto, Long userId) { String orderNo generateOrderNo(); BigDecimal totalAmount BigDecimal.ZERO; ListOrderItem itemList new ArrayList(); for (CartItemVO cart : dto.getCartItems()) { Product product productMapper.selectByIdForUpdate(cart.getProductId()); if (product null || product.getStatus() ! 1) { throw new BusinessException(商品不存在或已下架); } if (product.getStock() cart.getQuantity()) { throw new BusinessException(商品「 product.getName() 」库存不足当前库存 product.getStock()); } int updated productMapper.decreaseStock(product.getId(), cart.getQuantity()); if (updated 0) { throw new BusinessException(商品「 product.getName() 」库存扣减失败); } totalAmount totalAmount.add(product.getPrice().multiply(new BigDecimal(cart.getQuantity()))); // 组装订单明细对象并加入到itemList } OrderInfo order new OrderInfo(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(0); order.setReceiverName(dto.getReceiverName()); order.setReceiverPhone(dto.getReceiverPhone()); order.setReceiverAddress(dto.getReceiverAddress()); orderMapper.insert(order); itemList.forEach(item - { item.setOrderId(order.getId()); orderItemMapper.insert(item); }); cartMapper.deleteByUserAndProductIds(userId, dto.getProductIds()); return order.getId(); }这段逻辑里selectByIdForUpdate是关键点。FOR UPDATE锁定被选中的商品行同一件商品在这个事务提交前不会被其他线程再次读到旧库存值。decreaseStock的SQL是UPDATE product SET stock stock - #{quantity} WHERE id #{id} AND stock gt; #{quantity}这条语句自带条件判断即使没有锁多线程同时执行时也只会有一个请求成功配合行锁做到双保险。注意事务必须包含decreaseStock后的异常处理一旦后面生成订单明细失败rollbackFor Exception.class让库存回滚不产生幽灵订单。提示不要在Controller里做库存判断那是无效的——并发场景下两个请求同时通过判断库存仍然会被扣成负数。4.4 模拟支付与订单状态流转答辩演示时不可能真的接入支付宝我通常加一个「模拟支付」接口订单状态从0待付款换成1待发货同时写入pay_time。后台管理员登录后有一张「待发货订单」列表点击发货按钮把状态改为2待收货并写入deliver_time。再做一个「确认收货」按钮用户端点击后状态变为3已完成。这四个状态串起来就是完整的订单生命周期也覆盖了表设计里状态字段的全部取值。订单状态的变更我在Service里做了状态机校验只有当前状态等于预期值才能流转到下一步比如发货操作只允许从1转到2直接从0跳到2就报错。状态机用最简分支判断即可if (!Objects.equals(order.getStatus(), 0)) { throw new BusinessException(当前订单状态不可支付); }5. 避坑指南家具销售系统中的五处高频翻车现场5.1 图片上传后页面不显示路径总是404现象后台管理上传商品主图成功但前台商品列表页图片裂开浏览器控制台提示404。原因我在早期版本里把图片存到了项目源码目录下的src/main/resources/static/upload全量打包重启后文件被清掉而且IDEA的资源编译目标目录可能不包含这个路径。第二个坑是拼接图片URL时写死localhost:8080部署到云服务器后路径全废。解决把上传目录配置在application.yml里用绝对路径指向项目外的目录比如D:/furniture-upload/Windows或者/data/furniture-upload/Linux。然后配置一个WebMvc资源映射把/upload/**指向这个本地目录。这样打包重启不影响图片部署到服务器只需要把整个目录拷过去。记住main_image字段直接存/upload/xxx.jpg页面用th:src{${product.mainImage}}渲染引擎会自动拼接上下文路径。5.2 订单号过长、重复或直接用自增id暴露销量现象订单详情感perception不对或者两份订单的展示编号一样又或者答辩时评委看到订单号是1、2、3追问是不是可以直接猜出系统卖了多少单。原因直接用数据库自增id当订单号是最省事但最不专业的做法。自增id在同一张表内唯一没问题但对外暴露业务量且让爬虫可以遍历所有订单不加前缀的话多个系统服务拼接时容易冲突。解决用yyyyMMddHHmmss 三位随机数 用户id后四位拼业务订单号落库前查重一次数据库侧再建唯一索引兜底。这样的订单号可读性好能看出下单时间也不会暴露整体销量。生成代码放在Service层不要写在Mapper里。5.3 中文乱码页面、数据库、控制台三处各乱各的现象页面提交的中文商品名存进MySQL变成???控制台打印的SQL日志中文乱码页面显示正常但导出Excel时乱码。原因三层编码不一致。数据库连接串没有characterEncodingutf8MySQL表的字符集不是utf8mb4Thymeleaf模板文件保存编码不是UTF-8。这三个问题单独发生和同时发生的处理方式不同排查时先把表的charset查一遍。解决建表统一用utf8mb4连接串加characterEncodingutf8模板文件用IDEA右下角确认编码为UTF-8server.servlet.encoding.forcetrue强制请求和响应编码。想验证是否修复后台新增一个中文名称的商品再到mysql客户端执行SELECT name FROM product WHERE id 最新一条看到中文而不是问号才算通过。注意MySQL 8.0驱动默认utf8mb4但老版本驱动仍需显式指定字符集。5.4 并发下单导致库存变负数现象用JMeter或者十几个人同时抢一件商品时库存变成负数订单还能创建成功。原因Controller层先查库存、判断大于等于数量再执行update扣减。两个请求同时查到的库存剩余都是1都能通过判断然后各自扣掉1库存变成-1。判断和扣减不是原子操作。解决把「判断库存是否充足」和「扣减库存」合并成一条SQL即UPDATE product SET stock stock - #{quantity} WHERE id #{id} AND stock gt; #{quantity}返回值是影响行数。如果影响行数为0说明库存不够直接抛异常回滚事务。再多并发也不会出现负库存因为数据库的行锁让这条SQL在同一商品上串行执行。这条方案比悲观锁配合编程式判断更简洁也更容易在答辩时讲清楚。5.5 部署后8080端口被占用或访问不了现象本地运行正常部署到Linux服务器后浏览器访问不了或者启动提示Port 8080 was already in use。原因服务器安全组没有放行8080端口或者另一个Java进程占用了端口。解决先用netstat -tlnp | grep 8080检查端口占用如果是自己残留的进程kill掉后重启应用如果安全组问题去云控制台放行端口。同时为了让部署更稳建议启动脚本里用java -jar furniture-sales.jar --server.port8080 amp;指定端口并用nohup挂后台。如果测试环境有多套服务把context-path设为/furniture能少很多冲突。提示Spring Boot默认内嵌Tomcat打包时用mvn clean package打成的jar在Linux上直接java -jar运行即可不需要再额外装一个Tomcat。把端口和数据库地址作为启动参数传入换环境时不用重新改代码。6. 从能跑到能交付提升演示说服力的四个关键设计6.1 数据填充造假数据也要造得专业家具销售系统最终要给人演示空数据库展示出来非常难看。我建议写一个DataInitializer脚本自动生成十几个分类、每个分类下若干个商品商品价格要符合市场价——沙发5000到10000元床垫1500到4000元书桌800到2500元。图片可以用占位图服务或者本地放几张免费素材图别用版权不明的商业图。库存建议留几个低库存商品比如库存3件和5件现场演示购物车提交时能展示库存扣减的效果。再用SQL脚本批量插入几十条历史订单让后台管理页面的订单列表和数据统计图表不空荡。6.2 页面精简别在样式上花太多时间但三个页面必须精致首页、商品详情页、购物车结算页是演示时评委或面试官看得最久的三个页面。首页要体现分类导航、特价专区、销量排序详情页要有图片展示、规格参数表、加入购物车按钮、库存状态结算页要有收货人表单、商品明细、合计金额。这三个页面建议用Bootstrap栅格布局保持简单干净不要去做花哨的动画或大图轮播动画卡顿会拉低整个演示的完成度。列表页的排序功能不要做成只有名字排序把「销量优先」「价格从低到高」「价格从高到低」三个都做上这在演示时是快速展示系统完整性的一个细节。6.3 答辩准备预判会被追问的三个问题第一个问题是「为什么不用Redis做购物车」答案要点是Redis适合分布式场景下的会话共享本系统单体架构下Session存储购物车已经足够引入Redis是过度设计但如果你使用了Redis那就准备好被追问数据一致性问题。第二个问题是「库存扣减怎么做并发控制」上面讲的update ... where stock gt; #{quantity}加事务就是这个问题的标准答案演示时可以用两个浏览器窗口同时下单验证。第三个问题是「订单状态为什么用数字而不是字符串」数字的好处是做状态机校验方便占用空间小索引效率更高但需要维护一个状态枚举类来保证代码可读性。6.4 打包部署用Maven一次打出可运行jar最后讲落地的工程习惯。mvn clean package打包后确认jar包在target目录里生成。Spring Boot的默认打包插件会把依赖一起打进去生成的是胖jar直接可以java -jar运行。部署时把数据库连接参数和上传目录配置放到application-prod.yml里启动命令加--spring.profiles.activeprod避免每次改环境都动配置文件。落库前的mvn test建议至少保留一个测试类验证「库存充足时下单成功」和「库存不足时下单异常回滚」这两个核心用例面试官翻到测试代码时会明显更认可你这个项目不是只写了CRUD。我个人的习惯是项目做完后把启动说明和数据库初始化脚本放在一个README.md里再配上几张页面截图。这种整理工作看起来不产生代码但每一次答辩、每一次把项目展示给别人的时候它省下的解释成本都远超过写它的时间。希望这篇笔记能帮你在做家具销售系统时少踩几个坑把精力留在真正能给系统加分的设计上——比如并发库存、状态机、订单号和数据的完整性上。本文还有配套的精品资源点击获取
返回列表