☰
从业务拆解到金额精度:宠物用品销售系统全栈设计与实现
2026/10/1 12:23:28 网站建设 项目流程

做毕业设计那会儿,一搜“Java 管理系统 设计与实现”,跳出来的基本都是商城、库存、办公系统这几个老面孔。宠物用品销售系统在其中不算最常见,但恰恰是这类“带点垂直场景”的题目,稍微做好一点就能拿得出手:它既有常规商城的商品、订单、库存链路,又因为“宠物用品”这个细分场景,多了宠物档案、养宠人群画像、精准推荐这些普通进销存没有的看点。把这套逻辑写进论文,比单纯堆增删改查要有价值得多。

我不打算再写一篇复读项目介绍式的“全方位解析”,网上那种模板已经够多了。这篇文章我按自己做这类系统时真实走过的路子来讲:业务边界怎么拆、技术栈为什么这么选、数据库表怎么设计才扛得住销售链路、核心代码的难点怎么破、一次线上金额差一分的bug完整排查过程,最后是论文和答辩的实操建议。想看代码和设计思路的,直接照着改就能用。

1. 先搞清楚这套系统到底“管”什么:业务边界拆解比写代码更费脑

1.1 为什么是宠物用品,而不是又一个“通用商城”

很多毕设选“通用商城系统”,结果做出来就是一个加了购物车和订单的CRUD,导师问一句“你的系统解决了什么具体问题”就答不上来。宠物用品销售系统的优势是赛道自带约束,约束就是需求,需求就是论文的章节。

宠物用品本身有几个特点,直接影响系统设计:

  • 宠粮、零食这类标品复购率高,用户容易按月囤货,所以会员等级和折扣体系就有存在的意义。
  • 驱虫药、疫苗、保健品有保质期和存储要求,库存管理要比普通商品更严格,要能体现“效期预警”。
  • 猫砂、大袋狗粮这类重货物流成本高,订单里最好有配送方式或运费规则的设计点。
  • 用户有“我家养了金毛/布偶猫/龙猫”这样的宠物背景,宠物档案做出来之后,推荐、提醒、用药记录都能挂上去。

把系统定义为“普通商城+宠物档案+经营报表”,论文里的创新点、系统的“智慧”二字就都有了落脚点。做出来的东西不是大路货,答辩的时候故事也能讲得圆。

1.2 六张牌:核心业务模块划分

我当时把系统拆成六个模块,每个模块对应论文里的一章实现内容:

模块核心职责需要注意的业务细节
用户与会员管理注册登录、权限控制、会员等级用JWT做无状态登录,管理员和普通用户分角色
商品管理分类、品牌、规格、上下架、库存单价一律用DECIMAL,别用浮点,后面会专门讲
宠物档案管理品种、年龄、体重、绝育状态、过敏史这个模块是推荐功能的数据底座
购物车与订单购物车合并、下单、支付、订单状态流转订单号要有唯一业务号,订单明细里要存商品快照
促销管理会员折扣、满减活动、限时折扣活动时间重叠时要有优先级判断
统计分析销售趋势、TOP10商品、滞销品、库存预警Sql统计为主,图表用前端组件展示

这六个模块做完,功能层面已经比单纯的商品管理+订单管理完整一个档次。更重要的是,模块之间的数据流动关系清晰了,数据库设计才不容易乱。

1.3 “智慧”两个字具体落在哪个功能上

题目里有“智慧管理”四个字,答辩时很容易被问“智慧在哪里”。我的理解是,这部分不需要上什么人工智能,能用数据驱动业务就够了:

一是销售分析与库存预警。按月统计销售趋势、按品类统计毛利、计算滞销商品(比如近30天销量低于阈值)、库存低于安全线自动生成采购建议清单。这些功能用SQL就能实现,但效果很直观。

二是基于宠物档案的推荐。系统知道用户养的是3岁金毛还是1岁英短,就能按品种和年龄段推荐对应的粮、零食、驱虫药。推荐逻辑可以先用标签匹配实现——给商品打“幼犬、成犬、全猫期、泌尿护理”之类的标签,宠物档案里存品种和月龄,通过标签匹配捞商品,再按销量排序。这一套逻辑下来,“精准营销”就落地了。

这个模块做出来,系统的业务价值就超过了“卖货工具”,变成了能辅助店铺决策的经营系统。

2. 技术选型不必追新:Spring Boot + MyBatis Plus + Vue 的取舍逻辑

2.1 单体应用在这个场景下为什么够用

现在很多毕设喜欢堆微服务,一上来就是Spring Cloud Alibaba全家桶。我的建议很直接:除非你的题目明确要求微服务,否则不要自己给自己找麻烦。

宠物用品销售系统这个体量,单体能覆盖一切需求,而且事务边界清晰。下单扣库存、支付回写订单,这样的强一致操作在单体里就是一个本地事务,出现问题也好排查。微服务要考虑服务注册发现、远程调用、分布式事务、链路追踪,答辩时你要是没把分布式事务讲明白,这反而是减分项。

如果被评委问“为什么不用微服务”,你可以这样回答:当前业务规模下单体架构足够支撑,但我在模块边界上做了预留——订单、用户、商品分别独立分包,后续业务量增长时,可以按这几个边界拆分成独立服务。这个回答既诚实又显地有思考。

2.2 后端技术栈清单与其版本选择

我个人用的这套组合,是经过验证的稳定方案:

  • JDK 1.8 / 11,建议1.8,兼容性最好,答辩环境不容易出问题
  • Spring Boot 2.7.x,不用最新的Spring Boot 3.x,因为3.0要走Jakarta命名空间,很多老教程和示例直接失效,没必要冒险
  • MyBatis Plus 3.5.x,单表CRUD连SQL都不用写,复杂统计可以写自定义XML
  • MySQL 8.0,存储引擎InnoDB
  • Redis 可选,可以用来存验证码、缓存购物车,放进项目里能让技术栈更好看
  • JWT + Hutool 做登录鉴权和工具集
  • Lombok 减少样板代码

单元测试、全局异常处理、统一返回结果集R对象,这些工程化的小东西一定要有。我看到很多毕设代码,Controller里直接返回Map,异常处理完全没有,这样的代码就算功能全部跑通,也经不起细问。

2.3 前端与部署方案

前端我用的Vue 3 + Element Plus + Vite,也可以用Vue 2 + Element UI,看你自己哪个熟。管理后台用一套Vue页面搞定,如果还想加亮点,可以再加一个uniapp写的小程序端,主要做宠物档案录入和商城浏览、下单。这一部分工作量不大,但论文里能多一章“多端交互”。

部署方式一定要简单再简单:后端打jar包,前端build之后由Nginx托管静态文件,数据库用一个本地MySQL实例。数据库初始化脚本里要准备好测试数据,不然演示时现造数据非常尴尬。

后端包结构按模块分包,不说多优雅,起码别人一眼能看懂:

com.pet.shop ├── common # 通用类:异常、结果封装、工具类 ├── config # 配置类:拦截器、跨域、MyBatis Plus ├── controller # 控制层 ├── service # 业务层 ├── mapper # 数据访问层 └── entity # 数据库实体

包名和类名规范,在论文的系统设计部分直接可以引用,评审看代码时第一印象会好很多。

3. 数据库设计:库存、订单、宠物档案之间的数据关系是系统命门

3.1 核心表结构与职责

做销售类系统,数据库设计的好坏决定了后面开发是顺坡走还是天天踩坑。我在设计宠物用品销售系统时,最终保留了7张核心表:

表名职责关键字段说明
users用户、会员信息包含member_level,等级直接关联折扣率
pet_profile宠物档案品种type、出生日期、体重、绝育状态
product商品主表unit_price DECIMAL(10,2),stock冗余,version乐观锁
orders订单主表order_no唯一索引,total_amount/discount_amount/pay_amount
order_item订单明细冗余商品名和单价快照,防止商品改价影响历史订单
promotion_activity促销活动活动类型、折扣率/满减阈值、生效时间
cart_item购物车user_id + product_id,数量

其中orders和order_item的字段值得展开看。订单主表里可以冗余“总收入”,但要能把discount_amount和pay_amount分开,这就是会员折扣和活动折扣的落点。

CREATE TABLE `orders` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '业务订单号', `user_id` bigint NOT NULL, `total_amount` decimal(10,2) NOT NULL COMMENT '折扣前总额', `discount_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '优惠总额', `pay_amount` decimal(10,2) NOT NULL COMMENT '实付金额', `order_status` tinyint NOT NULL DEFAULT 0 COMMENT '0待支付1已支付2已发货3已完成4已取消', `create_time` datetime NOT NULL, `pay_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

order_no要当业务主键来对待,不能依赖数据库自增id对外暴露。唯一索引可以拦住重复下单的异常情况。

3.2 库存扣减不能“先查再改”,要直接走条件更新

这个地方是很多毕设最容易出问题的点。先SELECT stock,再在Java里判断库存够不够,然后UPDATE,高并发下一定会超卖。正确的做法是,把判断条件放进UPDATE语句里,让数据库做原子操作:

UPDATE product SET stock = stock - #{quantity}, version = version + 1 WHERE id = #{productId} AND stock >= #{quantity}

然后在MyBatis的Mapper方法里,通过受影响的行数判断扣减是否成功:

int affected = productMapper.deductStock(productId, quantity); if (affected == 0) { throw new BizException("库存不足或商品已下架"); }

受影响行数为0,要么库存不够,要么商品状态异常,这时直接中断下单流程。这里再把version当做乐观锁字段,配合条件更新,就形成了双保险。这块内容写进论文,再配一张时序图,放在“库存设计”小节,是非常实的干货。

3.3 订单与明细的写入策略:两次写,一次事务

下单过程要保证三件事的一致性:扣库存、生成订单主表、生成订单明细表。它们必须在同一个事务里,任何一个失败都整体回滚。

订单明细表里必须做“商品快照”。用户下单时商品单价是19.9元,一个月后店家把价格改成29.9元,历史订单明细里的价格不能被UPDATE跟着变。所以order_item里的商品名、单价、购买数量、折扣后小计,全部在下单时从product表copy进来,不再做关联查询。

订单状态流转用枚举在代码层控制,数据库里存int状态值:

public enum OrderStatus { UNPAID(0, "待支付"), PAID(1, "已支付"), SHIPPED(2, "已发货"), COMPLETED(3, "已完成"), CANCELLED(4, "已取消"); }

状态只能按顺序往下走,已取消只能从待支付和已支付状态流转过来,已发货不能一键回到待支付。这些规则写进Service层,防止接口被乱调用。

4. 销售链路的核心实现:购物车、会员折扣、宠物推荐三个难啃的点

4.1 购物车合并与价格计算的时序问题

购物车模块看起来简单,最容易被忽视的是“同一个用户重复添加同一个商品”。如果不做合并,购物车里同一个商品出现两三行,下单明细也跟着拆得稀碎,用户看到会直接骂人。

加购时的逻辑是:先查cart_item里有没有user_id+product_id的记录,有就UPDATE数量,没有才INSERT。这两个动作在Service层方法里操作,单条加购不需要跨表事务,用一个Mapper查询加两个更新方法就够了。

购物车列表展示时,每行小计要实时计算出来,能调商品当前价格。到订单确认页时,再一次性把全部购物车行读出来,校验每一行的商品状态和库存,重新计算总金额,让用户确认后下单。这个“重新计算再下单”的流程,比直接拿购物车金额下单要严谨得多,可以少很多纠纷。

4.2 会员折扣计算的精度问题:BigDecimal是唯一解

在Java里凡是涉及金额的计算,绝对不要用double和float,这是我接到的很多问题系统的第一宗罪。宠物用品系统里有会员折扣,有满减,有优惠券叠加,金额计算需要多个步骤,任何一步用了浮点,最终金额就可能差出一分钱。

关键的金额计算代码,我建议封装成一个工具类:

public class MoneyUtil { private MoneyUtil() {} public static BigDecimal yuan(BigDecimal amount) { return amount.setScale(2, RoundingMode.HALF_UP); } public static BigDecimal multiply(BigDecimal price, int quantity) { return yuan(price.multiply(BigDecimal.valueOf(quantity))); } public static BigDecimal discount(BigDecimal amount, BigDecimal rate) { return yuan(amount.multiply(rate)); } }

主流程里先计算商品小计,再叠加折扣,过程中先保留4位小数,最后入库时统一保留2位:

public BigDecimal calcOrderAmount(List<OrderItem> items, BigDecimal memberRate) { BigDecimal total = BigDecimal.ZERO; for (OrderItem item : items) { BigDecimal line = item.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity())) .setScale(4, RoundingMode.HALF_UP); line = line.multiply(memberRate) .setScale(4, RoundingMode.HALF_UP); total = total.add(line); } return total.setScale(2, RoundingMode.HALF_UP); }

中间过程保留4位,最后才保留2位,是为了避免中间步骤的舍入误差被逐步放大。数据库字段类型必须是DECIMAL(10,2),实体类字段必须是BigDecimal,两步缺一不可。

提示:MyBatis Plus做字段映射时,MySQL的DECIMAL类型如果被某个工具类自动转成了Double,你写再多BigDecimal防护也白搭。写完之后记得检查实体类所有金额字段的类型,别让自动生成工具给你挖坑。

4.3 基于宠物档案的用品推荐:让系统“懂”你的狗

推荐逻辑不需要做得多复杂,但要能讲清楚思路。我实现的时候分了三层:

  1. 给商品建立标签体系,比如“幼犬粮”“成犬粮”“猫咪泌尿健康”“关节护理”“驱虫”。
  2. 宠物档案里根据品种和月龄推导出宠物的生命周期阶段,比如0-12个月算幼年期,12个月以上就是成年期,7岁以上算老年期。
  3. 用户登录后,根据该用户关联的所有宠物档案,找出匹配的商品标签,按销量和库存排序取出前8个。

代码上就是一层查询匹配:

public List<Product> recommendProductsByPets(Long userId) { List<PetProfile> pets = petProfileMapper.selectByUserId(userId); if (pets.isEmpty()) { return productMapper.selectHotProducts(8); } return pets.stream() .flatMap(pet -> productMapper.selectByTagAndAge(pet.getType(), pet.getAgeMonth()).stream()) .distinct() .limit(8) .collect(Collectors.toList()); }

这个功能在论文“系统创新点”里可以大写特写,因为它直接跟普通电商的“猜你喜欢”区分开来了,是一个有业务数据支撑的推荐场景。

5. 一次真实的线上bug排查:会员折扣订单的金额差了一分钱

5.1 问题表象:客户反馈,某款狗粮打折后金额不对

当时上线后接到的第一个正经反馈:一个银卡会员买了某款进口狗粮,单价199.90元,买了3件,系统显示9折,实际支付金额却是539.72元。我拿计算器算了一下,199.90乘以3等于599.70,9折应该是539.73元,差了0.01元。

第一反应是先看是不是折扣率配置错了,但数据库里银卡折扣率确实写的0.9。再看订单明细,明细表里存的支付金额也是539.72,主表与明细一致。这就说明问题不是在订单汇总环节,而是在明细行算出来就错了一分钱。

5.2 排查链路:从订单数据一路追到实体类字段类型

我按这个顺序一层层查:

第一步,复算商品金额。在数据库里直接用SQL算199.90 * 3 * 0.9,结果539.73000,正确。

第二步,查order_item里保存的商品快照单价。结果发现存储的单价竟然是199.89999999999998,问题先藏在这里,数据在写入之前就已经不精确了。

第三步,追代码。查看下单流程里商品对象是怎么从库里查出来的。product表的unit_price字段类型本来是DECIMAL(10,2),但实体类里对应的字段被生成工具写成了Double。MyBatis查询时,DECIMAL转成Double,就变成了浮点近似值。

第四步,看计算链路。因为这个Double类型的price参与后续乘法和折扣计算,最终被转成BigDecimal时会带着一长串浮点噪声,四舍五入后正好多扣了用户一分钱。

根因就是那句老话:金额字段的存储类型和Java字段类型没有保持一致,浮点数的二进制存储特性导致精度损失。

类型对齐不彻底,所有后面的防御代码都是白做的。

5.3 修复方案与验证:类型收口、计算收口,一个不能少

修复动作分两步:

第一步,把product实体类里的unit_price字段从Double改成BigDecimal,全项目检索,凡是用Double和float表示金额的地方一律替换。表结构从DECIMAL到实体到计算链路,全链路类型对齐。

第二步,所有金额计算统一走MoneyUtil工具类,明确保留小数位数和舍入规则。禁止在业务代码里散落new BigDecimal(double)这样的写法,因为new BigDecimal(0.1)得到的依然是个带二进制噪声的数,必须用new BigDecimal("0.1")或BigDecimal.valueOf。

修复后我专门写了单元测试,覆盖普通商品下单、会员折扣、满减叠加、边界值正好整数的情况,确认全部通过后才发布。后面又随机抽样了近两个月的数据重新校验,没有再出现金额偏差。

5.4 这次排查换来的三个经验

第一,数据库DECIMAL映射到Java时,必须检查实体类字段类型,这是一票否决项。

第二,金额计算前要建立全项目的硬规矩:BigDecimal作为唯一金额类型,任何入库金额先setScale(2, RoundingMode.HALF_UP),中间计算时保留4位小数。

第三,排查问题不要一上来就怀疑“哪里写错了”,先把现象和数据对齐,再往代码链路里钻。从订单主表到明细到商品再到实体字段,一层层缩小范围,远比到处看代码高效。

这一课对毕设同样适用,因为你做的销售系统以后也是要真跑的,金额算错一个数,前面积累的信任全毁。

6. 论文写作和答辩演示的实操建议:代码能跑只是第一步

6.1 论文结构怎么安排才有逻辑

毕设论文的标准结构不需要创新,按下面这个顺序写,导师看起来省力,你写起来也不容易卡壳:

  • 绪论:背景意义、国内外研究现状。国内外现状部分可以从“电商系统发展”和“宠物行业数字化”两个角度写。
  • 相关技术介绍:Java、Spring Boot、MyBatis Plus、Vue、MySQL,每节至少写明为什么选它。
  • 需求分析:功能性需求用用例图呈现,非功能性需求写安全性、性能、可维护性。
  • 系统设计:总体架构、功能模块设计、数据库设计。这部分是全文重点,ER图一定要规范。
  • 系统实现:按模块贴代码和截图,这里是最容易凑字数但也最需要“说人话”的部分,贴代码不是目的,解释逻辑才是。
  • 系统测试:写测试用例表,包含用例编号、前置条件、输入、预期结果、实际结果、是否通过。

每一章的字数不用太均衡,重点放在系统设计和系统实现上,这两章加起来最好超过全文的一半。

6.2 画图和代码规范:评阅老师的第一印象

流程图、时序图、用例图,用Draw.io或Visio画就行。图片一定要自己画,不要截图别人的,评阅阶段最烦的就是图跟系统对不上。ER图里面标清楚主键、外键,一对多关系要画准确。数据库表的字段名和论文文字描述一致,不要正文里写member_level,表结构图里写user_level,这种不一致在评阅时特别显眼。

代码插入论文时,只保留核心代码片段,一个方法控制在20行以内,过长就拆掉。类名、变量名要见名知意,不要用a、b、c命名,也不要把一整段没有任何注释的代码扔上去。

6.3 答辩演示的正确顺序与高频问题准备

答辩演示建议按这条链路走,让评委10分钟内就能看清系统的全貌:

  1. 登录页面演示管理员和普通用户两种身份的区别。
  2. 商品管理中演示上架、改价、库存预警。
  3. 前台模拟用户加购、领券、下单、支付回调。
  4. 订单管理中跟踪这个订单状态流转。
  5. 宠物档案模块演示档案新增和推荐联动。
  6. 统计分析模块展示销售趋势和热销TOP10。
  7. 最后展示数据库表里的真实数据,证明不是纯前端mock。

准备期间要有意识地演练这几个高频问题:

  • 为什么选这个课题?答:垂直场景有业务价值,宠物档案与推荐是亮点。
  • 库存并发怎么保证?答:条件更新+乐观锁,结合代码说明。
  • 金额精度怎么解决?答:全链路BigDecimal,中间保留4位最后保留2位。
  • 数据量大了怎么办?答:从分页、索引、缓存三方面答,并说明当前库表加索引的实践。
  • 为什么不加购物车数量上限?答:可以在后端增加limit校验,防恶意刷单。

答辩前把演示环境提前准备好,数据库另起一个副本,不要用开发库当场操作,登录账号密码提前写在演示文档里,不然现场输错密码真的很尴尬。

这类销售管理系统做完,我最大的感受是:代码能跑只是最低门槛,真正拉开差距的是对业务链路和异常边界的理解。库存扣减为什么不能先查再改,金额为什么必须用BigDecimal,订单状态为什么不能随意跳转,这些问题想清了,哪怕功能少两个,也比堆了一堆但不解释原理的代码强得多。

最后再分享一个很朴素但很关键的经验:论文里的核心代码和数据库初始化脚本,答辩前一定要自己在干净环境里重新跑一遍。我见过不少人在评委面前因为环境依赖缺失、测试数据没导入而当场翻车,好好的系统成了一堆口头描述。把这一条守住,你的毕设就成功了一大半。

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

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

立即咨询