你手头的毕设题目如果是这么一类:“基于 SpringBoot 的微信小程序批发零售业商品管理系统”,甚至后缀还带着“社区团购库存订单一体化”“SCM 供应链轻量运营平台”,那咱们要聊的东西基本就是同一套东西。这个题目其实不是让你做一个大而全的电商商城,而是要把批发零售行业里最关键的“商品—库存—采购—订单—履约”串成一条可控的链,再用小程序去承接门店、团长、消费者这些角色的日常操作。SpringBoot 负责提供稳定接口和业务规则,小程序负责把人拉进来点一点,SCM 是背后那套供应链管理的思路。它解决的是传统批发商信息割裂的问题:门店开了一单,总部不知道库存变化;采购下了单,仓库不知道货什么时候到;社区团购的订单和门店零售订单又对不上账。把这几条线统一进一个平台,就是项目真正的价值。
如果你是在准备毕业设计,后端基础一般,又想完整体验一次前后端联调,甚至希望答辩时能讲出几个有深度的设计点,这个题目很适合。这篇内容不会只告诉你建几个表、写几个接口,而是会从题目拆解、系统架构、核心业务实现、小程序端踩坑,一直讲到部署上线和答辩准备,尽量让你拿着思路就能把项目落地。
1. 项目定位与功能边界:先把题目翻译成人话
拿到这种标题,最怕的就是被“供应链”“SCM”这种词吓住。计算机毕设里说的 SCM(Supply Chain Management),侧重点不在于物流运输、生产排程那些重型概念,而是强调你从供应商到仓库、再到门店或消费者手里这条链路上,每一环的数据是能对齐的。换句话说,你做的不是一个孤立的销售网站,而是能让“进销存”三个字运转起来的运营系统。
1.1 它到底在解决什么问题
批发零售业务有个很典型的困境:上游是几十个供应商,需要询价、下单、收货、对账;中间是仓库或者门店,会有一堆 SKU 在进出;下游既有固定来拿货的批发客户,也有通过微信群拼单的社区消费者。如果没有统一系统,采购员可能拿着 Excel 找供应商对货,仓库管理员自己记出入库账,前台开完单也不知道这批货是不是被另外一个人订走了。系统最核心的任务是把这些动作全部落到同一套数据上。具体来说,主要打通三条主线。
第一条是采购主线:供应商供货,采购员创建采购单,仓库到货后做入库,库存增加,同时形成供应商的应付款记录。第二条是库存主线:库存要能回答“某个 SKU 现在有多少、在哪个仓库/门店、最近进出过多少”,所有数量的变动都要有流水可查。第三条是销售主线:门店零售开单、社区团购下单、用户自提或配送,订单创建后要么占用库存、要么在核销时扣减库存,最终形成销售出库记录。三条线一旦连贯,商品档案、供应商档案、订单明细、出入库流水这些基础表自然就浮出来了。
1.2 为什么是 SpringBoot 加微信小程序这套组合
这几年高校毕设里最容易过审的组合就是 SpringBoot 配微信小程序。原因很实在:SpringBoot 生态成熟,网上案例多,面试和答辩老师都熟悉,你出问题也好查;内置 Tomcat、自动配置这些机制让你不用花大量时间在环境搭建上,专注写业务逻辑。微信小程序则胜在体验成本低:客户不用装 App,微信里搜一下或者扫个码就能用;对毕设演示来说,老师手机上扫码就能体验,这种感官冲击比电脑上一个后台管理页面强很多。当然,个人主体的小程序在支付和部分接口上会有限制,我后面会专门说怎么在设计上规避这个问题。
另外,SpringBoot 版本建议选 2.7.x,而不是一上来追新到 3.x。现在很多教程和依赖包还停留在旧 API 上,SpringBoot 3 不仅要求 JDK 17,而且把 javax 包全部换成了 jakarta,网上很多老代码直接粘过来会编译失败。基础一般的情况下没必要给自己挖这个坑,2.7.18 足够表现你自己的设计能力。
1.3 模块怎么切才不会给自己挖坑
这类毕设最容易出现的问题是把范围越铺越大,最后文档里写了十几个模块,实际代码只有几个 CRUD 页面。我的建议是核心模块守住六个就够了。商品与供应商档案管基础数据,采购与入库管补货动作,库存与调拨管数量变化,零售订单与售后管线下销售,社区团购管活动、成团和自提核销,系统管理管用户角色权限。这里我把“社区团购”单独拎出来,是因为它和你常规的商城购物流程不一样,它涉及拼团状态、自提点、团长核销,不拆清楚很难写明白。
至于要不要做分销裂变、多级代理、复杂的应收应付、车队物流轨迹,我建议统统别碰。功能越复杂,你在论文里要解释的商业逻辑就越多,代码出 Bug 的概率也越高。毕设答辩时老师更看重的是你有没有把一个核心闭环跑通,而不是你菜单里有多少个“管理”。换句话说,功能做减法,但数据深度要做加法,尤其是库存成本和订单状态机,这两个点足够让你在答辩时拉开差距。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构与角色权限设计:先想清楚再动手写代码
很多同学拿起 SpringBoot 就开建表,结果写着写着发现角色权限乱了,库存表也不知道该放哪个字段。所以第二部分先把架构、角色和数据库的关键决策讲清楚,你后面写代码才会顺。
2.1 单体服务已经够用,别急着上微服务
先明确一点:这个系统用单体应用就够了,不需要 Nacos、OpenFeign、网关那一套。答辩时如果老师问你为什么不用微服务,你可以理直气壮地说:当前业务体量、开发团队规模都撑不起微服务的复杂度,强行拆分会引入分布式事务、服务治理这些额外成本,反而是过度设计。做一个单体 SpringBoot 应用,内置 Redis 做缓存和 Token 管理,MySQL 存业务数据,文件上传走本地目录或者对象存储,就已经覆盖了项目全部需求。
整体请求链路是这样的:微信小程序前端通过 wx.request 发送 HTTP/JSON 请求到后端 Controller,Controller 做参数校验后交给 Service 层,Service 层执行业务规则并通过 Mapper 操作数据库,返回统一响应体给小程序。中间如果有需要控制的角色权限,就在拦截器或者 WebMvcConfigurer 中统一处理。代码层面我会按 common、config、controller、service、mapper、entity、dto、vo 这种层次分包。这样分层看起来是老生常谈,但它的好处是你写论文时可以直接画一张分层架构图。
Redis 在这个项目里不是必须,但接了会显得完整。我一般用它缓存微信登录后的 session、验证码、热点商品的库存信息,以及一些频繁查询的分类数据。注意,服务端接口要保证幂等性和事务控制,尤其是在创建订单和扣库存的接口上,Redis 不能替代数据库事务。
2.2 用户角色到底怎么拆
系统里至少有四类使用者:总部管理员、门店员工/仓库管理员、社区团购团长、普通消费者。因为你既做批发零售又做社区团购,不能只用“普通用户”一个角色糊弄。实际开发中,建议给后端管理系统和小程序端分开设计权限模型。后台管理端用经典的 RBAC 权限模型:用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。小程序端则轻量化一些,用户表里加 role_type 字段,比如 1 表示店主/收银员,2 表示团长,3 表示普通消费者。
为什么小程序端不把 RBAC 做成和后端一样?因为小程序端触点更适合“看菜单”而不是“管权限”。普通消费者看到的是首页、团购活动、订单、我的;团长额外看到自提点管理、核销列表;门店员工看到收银台、库存查询。你完全可以通过登录后返回的角色信息,在小程序端动态渲染底部 TabBar 和首页功能入口。这样用户感知更直接,代码也好维护。权限校验的实现,我比较推荐自定义拦截器加注解的方式,不引入 Spring Security 全家桶。写一个 @RequireRole({"STORE","ADMIN"}) 注解,在拦截器里解析 Token 中的角色并校验,核心逻辑一两个类就够了,答辩时你能讲明白原理比背一堆 Security 配置要强得多。
2.3 数据库设计里的四个关键决定
数据表结构是这类项目最能看出功底的地方。我强烈建议你在建表之前先想清楚四件事,不然后面返工很痛苦。
第一,商品 SKU 和库存不要混在一张表里。商品表存通用信息,比如名称、分类、主图、品牌;SKU 表存商品的具体规格、条码、进价、售价、当前库存、预警库存。“农夫山泉 550ml”和“可口可乐 330ml”是不同商品,同一个商品也会有“整箱”和“单瓶”的差异,把规格和数量都放在商品表里,后面根本没法管库存。第二,采购订单和采购入库单最好分表。采购订单是“计划从供应商买什么”,入库单是“实际到了什么货”,一次采购可以分多批到货。毕设里如果只做一张表,会被问到“部分到货怎么处理”时难住。第三,销售订单上要带业务类型,用 order_type 区分门店零售、社区团购这两种场景,同时记录下单人、自提点或门店 ID、核销人,方便后续统计。第四,所有业务表都建议逻辑删除,加 deleted 字段,同时加上 create_time、update_time、version 字段,版本号在后面做乐观锁扣库存时会用上。
以 SKU 表为例,核心字段大概是:
sql复制CREATE TABLE sp_goods_sku (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
sku_code VARCHAR(64) NOT NULL COMMENT 'SKU编码',
goods_id BIGINT NOT NULL COMMENT '商品ID',
spec_name VARCHAR(64) COMMENT '规格名:如整箱/单瓶',
barcode VARCHAR(64) COMMENT '条码',
unit VARCHAR(16) COMMENT '计量单位',
stock INT DEFAULT 0 COMMENT '当前库存',
warning_stock INT DEFAULT 0 COMMENT '预警库存',
cost_price DECIMAL(10,2) COMMENT '成本价',
sale_price DECIMAL(10,2) COMMENT '销售价',
version INT DEFAULT 0 COMMENT '乐观锁版本号',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标志',
create_time DATETIME,
update_time DATETIME
);
库存的冗余字段和维护流水之间并不矛盾。SKU 表里的 stock 是一个可查询的冗余值,每次库存变动时除了更新这个字段,还要往库存流水表 sp_stock_record 里插入一条记录。流水带有业务单据号、变动类型,比如采购入库、销售出库、盘点调整、退货入库。这样既能实时查库存,又能审计追溯。
3. SpringBoot 服务端核心实现:从框架到业务闭环
服务端是整条业务链路的发动机。我按照工程搭建、登录鉴权、库存订单、采购成本四个维度讲,每个维度都会把关键设计给你讲透。
3.1 工程骨架与依赖版本经验
我强烈建议你创建项目时把 Java 版本、SpringBoot 版本、MySQL 驱动版本一次性选对。推荐组合是 JDK 1.8、SpringBoot 2.7.18、MySQL 8.0.33、MyBatis-Plus 3.5.x、Redis 客户端使用 Spring Data Redis、JWT 库选择 jjwt 或 java-jwt。MyBatis-Plus 是个省事的选择,单表 CRUD 不用写 XML,内置分页插件也能直接满足商品列表、订单列表这些页面。但要注意,它只是帮你省了重复代码,核心的库存扣减、状态流转必须自己写。我见过不少同学用代码生成器把表全部生成一遍,然后告诉我“系统做完了”,那种项目答辩很容易被问到“你做了什么”时答不上来。
Controller 层不要大包大揽。每个接口的入参用 DTO 接收,出参用 VO 封装,Controller 只负责校验和调用 Service,真正业务规则全部写在 Service。统一响应体我习惯设计成这种结构:
java复制public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> ok(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("success");
result.setData(data);
return result;
}
public static <T> Result<T> fail(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
}
配合全局异常处理器,所有业务异常都能统一返回给前端,小程序端只需要判断 code 就能弹出错误提示。这个虽然不是高深技术,但能让代码风格非常整洁,论文里也值得写一笔。
3.2 登录鉴权不只有账号密码
这个系统会有两类登录:小程序用户登录和后台员工账号登录。小程序用户登录第一步是拿到前端传来的 code,后端再调用微信官方接口,用 appid 和 appSecret 换 openid 和 session_key。这一步要注意 appSecret 绝对不能放在小程序端,也不能写进前端工程,它只属于服务端配置。拿到 openid 后,先去用户表查这个用户是否已经注册过,没有就用 openid 自动创建一个注册用户。然后用 JWT 生成一个 token 返回给小程序,包含用户ID和角色。
后台管理系统就比较传统了,用账号加密码登录。密码存储要加盐后做 BCrypt 哈希,不要存明文。登录成功后同样签发 JWT。为了统一,给用户表加一个 source_type 字段,1 表示后台员工,2 表示微信小程序用户,openid 单独存一列并建唯一索引。这样可以共用一张用户表,但角色体系不会混乱。
JWT 的有效期建议设置成 7 天,避免小程序用户频繁登录。但 JWT 过期以后只能重新登录,如果你是用户意识较强的系统,也可以加 Redis 维护 refresh token,不过毕设阶段没必要做得这么深。拦截器实现时要注意放行路径,比如登录接口、微信支付回调接口、Swagger 文档页,凡是不能带 Token 访问的路径都要放进白名单。我见过很多项目联调时卡在 Swagger 上,最简单的做法是在 WebMvcConfigurer 里显式添加排除规则。
3.3 库存扣减与订单状态机怎么结合
库存和订单是核心中的核心,也是最容易踩坑的地方。很多人第一反应是先查询库存够不够,再执行 update 扣减,这个思路在并发场景下必然出问题。两个请求同时查到一个剩余库存是 10,同时判断“够”,然后都扣了 5,最后库存可能是 5 而不是 0,甚至可能是负数。正确做法是把扣减动作和条件判断放到同一个 SQL 里,让数据库帮我们守住底线:
sql复制UPDATE sp_goods_sku
SET stock = stock - #{quantity},
version = version + 1
WHERE sku_id = #{skuId}
AND stock >= #{quantity}
AND deleted = 0
这个 SQL 返回的影响行数是 1,表示扣减成功;返回 0,表示库存不足或记录不存在,此时直接抛业务异常回滚整个事务。这里不加分布式锁,也不加 synchronized,就是因为 SQL 本身就是原子操作。用 version 字段是为了防丢失更新,属于乐观锁思路。如果你还想给项目中加更多亮点,可以把库存扣减前先缓存到 Redis,再用 Lua 脚本保证原子性,但那是锦上添花,不是必须。真正必须的是你要在论文里说清楚“为什么不能用先查后改”。
订单状态也不能用几个 if 到处改。建议定义一个状态常量类,把状态机写清楚。以普通销售订单为例,完整状态可以分成待支付、待发货、待收货/待自提、已完成、已取消、退款中、已退款。社区团购订单则多一个待成团状态,拼团成功以后转入待发货;拼团失败则自动取消并触发退款。每次修改状态时,不能只 set 一个 status 字段,最好在 SQL 里带上当前状态:
sql复制UPDATE sp_sales_order
SET status = #{newStatus}
WHERE id = #{orderId}
AND status = #{expectStatus}
这条 SQL 就是典型的 CAS 思路,意思是我只允许从“我预期中的状态”跳转到新状态,如果状态已经被其他人改过,这次更新就会失败,避免重复操作。比如用户连续点了两次取消,第一次把状态从待支付改成已取消,第二次再拿“待支付”条件去更新,影响行数是 0,就能安全拦截。社区团购的成团判断,可以用 @Scheduled 定时任务每 30 秒扫描一次活动,把到达截止时间且参团人数达到下限的活动标记为已成团,把达不到下限的订单批量改成已取消。
3.4 采购入库与移动加权平均成本
进销存的“成本”不是商品表里一个字段那么简单。同一款商品不同时间从供应商采购的价格可能不一样,如果你在销售订单里直接记一个“当前售价”,算毛利时会很不准确。毕设里我推荐实现“移动加权平均成本”,原理很好懂:每次采购入库时,用原来的库存总成本加上本次入库总成本,再除以新的总数量,得到新的平均单价。
举个例子,原来库存 10 件,结存成本单价 5 元;今天采购入库 100 件,单价 8 元。那么新成本价就是 (10 * 5 + 100 * 8) / (10 + 100) = 7.727。后续销售出库时,出库成本按 7.73 记账,毛利就是销售额减去 7.73 乘以数量。这个计算不难,但能体现你懂供应链的财务逻辑,答辩老师通常会比较认可。采购单状态我建议这么设计:草稿、待审核、部分收货、已收货、已完成。采购员创建采购单后,仓库人员看着实际到货情况填写数量,生成入库单,再更新 SKU 库存和平均成本。所有操作都包在一个事务里,只要一步失败就全部回滚。
4. 微信小程序端实现细节与经验
服务端再完善,小程序端一卡壳,整个演示都白搭。我拆几个重点页面和场景,每个都是真实项目里会遇到的坎。
4.1 页面结构、角色动态入口与自定义 TabBar
小程序页面结构不建议一把梭放在 pages 根目录,最好按业务拆分打包。比如普通消费者页面放根目录,门店相关页面放一个 packageStore 分包,团长和提货点相关页面放 packageGroup 分包。分包的好处是主包体积更小,启动更快,而且代码结构清晰。
如果不同角色看到的底部导航不同,默认的 tabBar 是做不到的,因为它是静态配置。这种情况就要用自定义 TabBar。你需要在 app.json 里配置:
json复制"tabBar": {
"custom": true,
"color": "#999999",
"selectedColor": "#ff6600",
"list": [
{ "pagePath": "pages/index/index", "text": "首页" },
{ "pagePath": "pages/order/order", "text": "订单" },
{ "pagePath": "pages/my/my", "text": "我的" }
]
}
同时,在小程序根目录创建 custom-tab-bar/index 组件。组件里可以拿到全局登录状态,根据角色动态渲染菜单项。这里最常见的问题是开发者忘记在 custom-tab-bar 所对应的 tabBar 页面里的 onShow 中调用 this.getTabBar().setData({ selected: index }),导致切换页面后底部高亮状态不对。这个坑虽然小,但演示时非常明显。
4.2 登录、头像昵称和手机号绑定
小程序端最怕一上来就弹授权框,很多用户在“拒绝授权”后就走掉了。现在微信官方也调整了策略:获取用户头像建议用 button 的 open-type="chooseAvatar",获取昵称用输入框让用户自行填写,不要用 wx.getUserProfile 去硬拿。这个改动对毕设很友好,你可以在“我的”页面让用户点一下授权头像并输入昵称,算是完善个人信息。
登录链路建议做成静默登录:启动时先检查本地缓存有没有 token,没有就触发 wx.login 拿 code,调后端登录
