☰
Spring Boot+微信小程序商家优惠活动系统:领券核销全栈实战解析
2026/10/3 3:09:44 网站建设 项目流程

如果你正在为毕业设计找 Spring Boot 加微信小程序方向的项目,看到“商家优惠活动”这个题目,我建议你先别急着把源码下载下来就跑。这个系统的名字看着简单,但它背后的业务闭环其实相当完整:商家创建活动、用户领券、到店核销、管理端做数据管理,这套链路包含了登录鉴权、并发控制、文件上传、列表分页这些毕业设计答辩时老师最喜欢追问的点。Spring Boot 负责的是活动数据、用户账号、商家管理的后端服务,微信小程序承担的是用户随手打开就能领取优惠、查看活动的触达入口,两者组合在一起,既覆盖了前后端分离开发的完整流程,又避开了 App 开发的高成本。这篇内容我会从需求拆解讲起,把模块设计、数据库建模、核心接口实现、真机调试这些环节逐个过一遍,适合正在做类似课题的学生,也适合刚入行想搞懂一个全栈小项目到底怎么落地的开发者。

1. 项目到底在做什么:需求拆解与系统边界

1.1 优惠活动业务的真实场景还原

先把这个系统说人话。一家线下奶茶店要做促销,老板在管理后台发布一条活动:“全场第二杯半价,限量 500 份,每人限领 1 张”。用户打开微信小程序,看到活动卡片,点进详情页了解规则,一键领取优惠券,券自动存进“我的券包”。用户到店消费时出示券码,店员在商家端输入券码或直接扫码核销,这条优惠链路就闭环了。整个过程中产生的数据,比如活动曝光了多少次、多少人领券、核销率多高,都要能查得到。

毕业设计选这样的题目非常聪明,因为它把真实商业场景里最核心的“发券、领券、用券”三个环节完整地搬到了系统里。你不需要做复杂的推荐算法,不需要搞支付对接,但已经能训练到 Web 开发里几乎所有常规能力:增删改查、权限区分、数据校验、并发扣减、异常处理。而且无论是写开题报告还是做答辩 PPT,这个业务故事都非常好讲,评委一听就知道系统在解决什么问题,不用你花半天去解释背景。

1.2 角色与核心用例的取舍

这类系统通常有三角色,设计思路上要把它们各自关心的内容画清楚。

普通用户只看三件事:有什么活动可以参与、怎么领券、领到的券在哪里用。所以用户端的用例就是活动浏览、活动详情、领券、查看我的券包、出示核销码。商家角色关心的是活动投放效果,用例集中在创建活动、编辑活动、下架活动、优惠券模板管理、核销记录查询。平台管理员则是兜底的角色,负责审核商家入驻、查看整体经营数据、处理违规内容。

不过我要提醒一句:很多同学做这种题目,最容易犯的错就是把管理端做得太大,用户端反而做得很草率。评委看的是完整度,不是字段数量。正确做法是先把“用户从浏览到核销”这条主链路跑通做到精致,再去补管理端的活动管理和核销查询。如果你的时间有限,管理员角色可以合并到商家角色里,通过角色字段区分权限,这是很多成熟源码的常见做法,也完全说得通。

2. 技术选型:为什么是 Spring Boot 加微信小程序这套组合

2.1 Spring Boot 成为毕业设计标配的三个理由

先说后端。Spring Boot 能成为计算机毕业设计里几乎“垄断”级的选择,靠的不是追新,而是它真的解决了写业务系统时最烦的那些事。

第一个理由是生态成熟,起步成本低。你引入一个spring-boot-starter-web就能把整个 Web 容器、JSON 序列化、参数校验全带起来,不用像传统 SSM 那样手动配置一堆 XML。第二个理由是内置 Tomcat,打包成 jar 就能跑,这在答辩现场特别重要。你不可能在评委面前现场配置一个 Tomcat 再等它启动,而java -jar一步到位,演示效果直接拉满。第三个理由,也是面试和答辩常被追问的点:Spring Boot 的自动装配原理。像@SpringBootApplication背后的@EnableAutoConfiguration,还有spring.factories机制,这些是能体现你真正理解框架背后机制的地方。

还有一个非常现实的原因:市面上的源码、教程、社区讨论绝大多数都围绕 Spring Boot,遇到问题搜得到答案。你在群里问一句“springboot 配置端口号怎么改”,立刻有人回你server.port。技术选型最怕不是“不够先进”,而是“没人踩过坑”。

2.2 微信小程序作为用户端的优势与限制

再聊前端载体。商家优惠活动这种场景,目标用户是线下消费者,他们不可能为了领一张券专门去下载一个 App。微信小程序最大的优势就是“用完即走、免安装”,用户扫码或者搜一下就能打开,天然适配这种轻量级营销场景。

另一个优势是登录体系。小程序端可以借助微信的wx.login拿到用户的微信身份标识,省去了自己搞一套账号注册、手机号验证的流程。你只需要把微信返回的openid和系统内的用户表做关联,就能识别唯一用户。这比从零写一套用户名密码体系简单太多,而且用户也懒得注册,体验更好。

但小程序不是没有坑。它的代码包有体积限制,WXML 的组件化能力也不如 Vue 在浏览器里那么灵活,开发者工具的表现和真机渲染还有差异。这些限制我在后面第 5 章会专门讲如何处理。选小程序做前端,等于你主动给自己加了“移动端适配”和“微信生态规则”这两门课,对毕设来说反而是加分项。

2.3 数据库与文件存储方案怎么选

数据存储这块,MySQL 加 MyBatis Plus 是绝对的主流组合。MySQL 不用多说,关系型数据库最适合这种强结构化业务。MyBatis Plus 则帮了大忙,它内置的BaseMapper让你连最基本的单表 CRUD 都不用写 XML,分页插件也是一个依赖就搞定。毕设阶段,它比原生 MyBatis 写出来的代码量少一半,而且代码风格非常统一,答辩时展示起来也清晰。

文件存储是很多人忽略的点。活动要传封面图,商品要传图片,如果你用本地目录保存文件,部署时有个坑:服务器重启或者换环境,图片路径会丢失。有的源码会引入 MinIO 来做对象存储,搜索热词里也有“minio 加入到 springboot”,说明这是大家关注的方向。MinIO 的好处是可以模拟一个完整的文件服务,支持桶管理和预签名 URL,能往论文里写“分布式文件存储方案”这种亮点。但我要说实话:如果你的毕设只是单机部署,本地存储加一个静态资源映射已经够用。我建议优先本地存储,答辩时如果老师问“如果线上有多台服务器怎么办”,你再把 MinIO 方案作为扩展设计讲出来,反而显得你有前瞻性。

3. 核心功能模块设计与数据库建模

3.1 管理端与商家端的活动管理设计

现在把系统拆开看内部结构。先说活动管理这个模块,它既是整个系统的数据源头,也是展示你数据库设计功力的地方。

活动表的核心字段大概这些:活动标题、活动简介、封面图 URL、优惠类型、优惠规则描述、活动开始时间、活动结束时间、总库存、每人限领数量、活动状态。为什么要把“总库存”放在活动表而不是优惠券表?因为一个活动对应一种优惠券模板,库存是模板的库存,不是用户券的库存。活动状态我建议直接用数字枚举:0 未开始、1 进行中、2 已结束、3 已下架,而不是用布尔值。因为活动的生命周期至少有四种状态,布尔值根本表达不了。

商家端的功能围绕活动展开:创建活动时填写基本信息,保存后可以选择“上架”或“暂存草稿”;活动进行中可以查看实时领取数量,但一般不允许修改库存规则;活动结束后可以看到统计报表。管理端则在商家列表里做审核操作,比如冻结违规账号、恢复账号。

3.2 用户端的核心流程设计

用户端的信息架构相对固定,主要四个页面:首页活动列表、活动详情页、我的券包页、核销码出示页。如果有条件,再加一个“我的”页面,展示个人头像昵称和领取记录。

首页活动列表只展示“进行中”的活动,这是很多新手容易忽略的逻辑。如果你把已结束和下架的活动都查出来,用户打开看到一堆过期券,体验很差。所以查询条件里一定要带上status = 1和当前时间在起止区间内这两个条件。活动详情页展示规则说明、剩余库存和领取按钮。领券按钮的交互有两种状态:未领取时可点,已领取后置灰并显示“已领取”,这个状态判断要在后端返回数据时就给到前端,而不是让前端自己猜。

核销码页面做起来要细心一点。最朴素的实现是进入页面时请求后端生成一个固定格式的券码,用二维码组件渲染出来,商家端输入券码后变更状态。稍微精致一点的实现会加入二维码过期时间,比如 5 分钟刷新一次,防止截图被盗用。这个细节如果你写进论文,属于很加分的“安全性设计”。

3.3 数据表拆分的几个关键点

围绕这条业务链路,数据库至少要有这几张核心表:用户表、商家表、活动表、优惠券模板表、用户优惠券表、核销记录表。

这里最重要的建模思想,是把“优惠券模板”和“用户持有的券”分层。一个活动对应一条优惠券模板记录,用户领取一次就在“用户优惠券表”里生成一条实例记录。为什么要这样拆分?因为一个用户可能领了同一活动的券,也可能领了不同活动的券,每张券都有自己的状态(未使用、已使用、已过期),这些状态必须挂在实例上,而不是挂在模板上。初学者最容易犯的错,就是把优惠券做成一张表,直接在活动表里加个字段记录领取人,最后做统计的时候数据一团乱。

另外每张表建议都带上create_time、update_time、deleted这几个通用字段,这是行业惯例,也是 MyBatis Plus 自动填充功能的用武之地。设计索引时注意两个高频查询:活动表按状态和时间查列表,用户券表按用户 ID 和状态查券包。这两个查询务必建联合索引,否则数据量一上来就会明显变慢。答辩时能说出“这条查询走了联合索引”这一句,专业度直接不一样。

4. 关键功能实操:从登录到领券全链路落地

4.1 登录态设计:从 code2session 到 JWT 拦截器

现在进入真正的编码环节。先用登录功能开场,因为它是后面所有接口的前置条件。

微信小程序的登录不是传统意义上的账密登录,而是走wx.login拿到一个临时code,传给后端,由后端调用微信的code2Session接口,用这个code换取用户的openid。拿到openid后先去用户表查一下有没有这个人,没有就自动注册,有就取出来。后端再把userId和角色信息封装进 JWT Token 里返回给前端,前端存到wx.setStorageSync,之后每次请求在 header 里带上Authorization。

public LoginResult login(String code) { // 调用微信接口换取 openid WxSessionResp resp = wxService.code2Session(code); if (resp.getOpenid() == null) { throw new BusinessException("登录失败,请重试"); } User user = userMapper.selectByOpenid(resp.getOpenid()); if (user == null) { User newUser = new User(); newUser.setOpenid(resp.getOpenid()); newUser.setNickname("微信用户"); userMapper.insert(newUser); user = newUser; } String token = JwtUtil.createToken(user.getId(), user.getRole()); return new LoginResult(token, user.getNickname()); }

后端再用一个拦截器统一校验:请求进来先取 header 里的 token,解析失败直接返回 401,解析成功就把userId放进ThreadLocal或请求属性里,供后续接口使用。这里有一个非常关键的安全细节:绝对不要把 openid 直接当作令牌传给前端。openid 是用户的业务身份,它本身没有过期控制,一旦泄露别人就能伪装成任意用户。用 JWT 的好处是自带过期时间,服务端无状态,分布式环境下也适用。

4.2 领券防超发:并发场景下的三种解法

领券是整个项目里技术含量最高的一个接口,也是答辩时老师最喜欢深挖的点。你可以先想一个问题:当 1 万人同时点“我要领券”的时候,数据库里这 500 份库存要如何保证不被超发?

第一种方案:数据库唯一索引。在用户券表加一个(user_id, template_id)的唯一索引,用户重复领同一张券时,插入直接报错。这个方案能挡重复领取,但挡不了库存超发。

第二种方案:乐观锁。在优惠券模板表加一个version字段,更新时带上版本号判断。这是很多源码的常见做法,但性能一般。

第三种方案,也是我最推荐在项目里落地的方案:用数据库的原子更新。不需要先查询库存再判断,直接把扣库存的 SQL 写成一条原子操作。

UPDATE coupon_template SET stock = stock - 1 WHERE id = #{templateId} AND stock > 0

配合事务注解:

@Transactional public ReceiveResult receive(Long userId, Long templateId) { // 校验用户是否已领取 Integer count = userCouponMapper.countByUserAndTemplate(userId, templateId); if (count > 0) { return ReceiveResult.fail("你已经领过这张券了"); } // 原子扣减库存,受影响行数为0说明库存不足 int rows = couponTemplateMapper.decreaseStock(templateId); if (rows == 0) { return ReceiveResult.fail("手慢了,已被领完"); } // 插入用户券记录 userCouponMapper.insert(...); return ReceiveResult.success(); }

这种写法的精妙在于,数据库的行锁本身就把并发串行化了,stock > 0的条件又是最后一道防线。即使一万个人同时请求,最终也只有 500 个人能扣减成功。如果评委继续追问“高并发怎么办,数据库撑不住怎么办”,你就可以说“生产环境可以引入 Redis 通过decr原子扣减,再把结果异步写回数据库”,这是非常标准的延伸答案。

4.3 首页活动列表的分页与加载更多

用户的手机屏幕就那么大,活动列表不可能一次性返回几千条。微信小程序里“上拉加载更多”是个非常常见的交互,做法就是前端在滚动到底部时触发onReachBottom,把当前页数加一,再请求下一页数据。

onReachBottom() { if (this.data.page >= this.data.totalPages) { return; } this.setData({ page: this.data.page + 1 }); this.loadActivities(); }

后端配合 MyBatis Plus 分页插件,接口接收pageNum和pageSize,返回标准的分页结构:当前页数据、总条数、总页数。前端拿到totalPages后判断是否还有下一页,如果没有,加载更多时就提示“没有更多了”而不是一直空转。

这里有两个细节值得注意。第一,后端返回值要统一封装,让前端能同时拿到列表数据和分页元信息。第二,列表接口一定要做缓存意识,比如查热门活动可以加@Cacheable,虽然毕设不强制,但你在论文里提一句“热点数据加缓存”能提升档次。分页列表看起来简单,但它同时涉及前后端参数约定、数据空状态处理、防重复请求,属于最简单的“全栈基本功”题目。

4.4 接口联调与网络请求封装

前后端联调阶段,沟通成本往往比写代码还高。为了避免后端参数名、返回结构反复变动,我强烈建议先约定好统一返回结构:

public class Result<T> { private Integer code; // 200 成功,400 参数错误,401 未登录,500 系统错误 private String message; private T data; }

所有接口一律返回Result<T>,前端拿到后先判断code是否为 200,再做后续逻辑。小程序端的请求封装也遵循同样的约定:

function request(url, method, data) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: method, data: data, header: { 'Authorization': wx.getStorageSync('token') }, success(res) { if (res.data.code === 200) { resolve(res.data.data); } else if (res.data.code === 401) { wx.navigateTo({ url: '/pages/login/index' }); } else { wx.showToast({ title: res.data.message, icon: 'none' }); } }, fail(err) { reject(err); } }); }); }

开发调试时,微信开发者工具有个“不校验合法域名”的开关,测试环境可以用 HTTP 调试。但真机预览和发布版本必须配置 HTTPS 合法域名,这是微信平台的硬性要求,很多人第一次真机测试白屏,八成就是忘了这一步。如果你要在局域网环境调试,记得勾选“开发环境不校验请求域名”,同时保证手机和电脑在同一网络下,后端启动的端口要让手机能访问到。

5. 踩坑实录与常见问题排查

5.1 Spring Boot 版本不是越新越好

我见过太多同学在选题初期图新鲜,直接上了 Spring Boot 3.x,结果项目跑到一半发现一堆老教程跑不通,痛苦不堪。这并不是 Spring Boot 3 不好,而是它的生态兼容性对新手不够友好。

Spring Boot 3.x 强制要求 JDK 17,并且把javax.*包迁移到了jakarta.*包名下。别看就改了个前缀,很多老版本的 MyBatis Plus、某些文件上传工具类都会因此直接报类找不到。更麻烦的是,Spring Security 6 的配置方式比 5 代差得非常多,DSL 风格完全变了,照着旧教程写基本是白费功夫。

我的建议很明确:如果只是做毕业设计,选 Spring Boot 2.7.x 加 JDK 8 或者 11,是最稳妥的搭配。工具类、教程、答案解析全是现成的。如果你确实想用 3.x 展示自己跟进了新技术,请务必选对配套版本,比如 MyBatis Plus 要用mybatis-plus-spring-boot3-starter这个专门适配 3.x 的包,数据库驱动要用com.mysql.cj.jdbc.Driver。这个决策要在项目初期就定下来,否则改到一半再换版本等于重写一遍。

5.2 开发者工具与真机预览的差异

代码在开发者工具里跑得挺好,一上真机就“翻车”,这是微信小程序独有的课题。最常见的问题有三个:顶部导航栏高度不一致、iPhone 底部安全区遮挡、图片显示异常。

顶部导航栏高度是 iOS 上非常典型的坑。如果你在页面里写了自定义导航栏,直接用固定数值去适配不同机型,几乎一定对不齐。正确做法是用wx.getWindowInfo()获取状态栏高度,再用胶囊按钮的位置动态计算导航栏高度。这也是为什么你搜“微信小程序顶部导航栏高度”会有那么多文章,因为这是每一个小程序开发者都绕不过去的问题。

还有体验版分发这个流程,很多同学不知道。开发完成后,在开发者工具点“上传”,在微信公众平台把上传的版本设为“体验版”,然后把体验版二维码发给同学,他们扫码后就能在微信里试玩你的小程序。要正式给几百个人用,那还要走提交审核、发布上线这套流程,毕业设计一般不需要走到那一步,体验版已经足够你收集试用反馈了。

5.3 常见问题速查表

我把做这类项目过程中最常遇到的问题整理成了一张表,你可以直接截图保存或贴进自己的笔记里。

现象可能原因处理办法
小程序请求后端返回 404后端服务没启动,或启动端口与前端配置不一致检查后端控制台日志,确认server.port的端口号
接口返回 401token 没传、token 过期、拦截器放行路径配置错误检查请求 header 里的 Authorization,确认登录接口返回的 token 是否被正确保存
所有数据不显示后端接口异常被全局异常吞掉,或数据库没有初始化数据先看后端日志,再用 Postman 直接测接口,确认数据表里有没有记录
图片上传成功后刷新页面图片消失本地存储文件路径丢失,或者重启后临时目录被清空改用本地固定目录存储并做静态资源映射,或者升级为 MinIO
列表加载更多时不断重复请求前端没有判断 totalPages,触底后一直累加页码后端必须返回总页数,前端在onReachBottom里加判断,请求过程中加 loading 锁
真机预览白屏未勾选开发环境不校验域名,或 HTTPS 证书不合法真机调试时勾选“不校验合法域名”,发布前配置合法 HTTPS 域名
优惠券被重复领取缺少唯一索引或未在领券接口做重复校验用户券表加(user_id, template_id)唯一索引,接口里先查再插入
库存显示负数扣库存没有加stock > 0条件用原子更新 SQL:UPDATE ... SET stock = stock - 1 WHERE id = ? AND stock > 0

这张表里的每一个问题,我都在实际带学生做项目时遇到过。“请求 404”这个排查过程尤其值得说一句:先确认后端进程还活着没有,再确认端口,最后才去看代码。顺序反了,你会浪费很多时间。

6. 源码到手后怎么快速看懂并二次开发

6.1 让毕设从“能跑”升级到“能答辩”

你拿到一份源码,先别急着改代码,按照下面的顺序过一遍,效率最高。第一步,把项目跑起来,用开发者工具打开小程序,完整走一遍用户领券、核销的流程,让系统先在你脑海里建立“它到底长什么样”的认知。第二步,打开数据库,看每张表的字段和关系,重点理解活动表、优惠券模板表、用户券表这三个是怎么关联的。第三步,从登录接口开始,沿着请求链路把后端代码过一遍,搞清楚每个 controller、service、mapper 的调用关系。第四步,找一个你想要改进的小功能动手,比如增加一个“活动搜索”或者“分类筛选”,从小改动开始熟悉代码风格。

这里有个很重要的心态:不要想着把整份源码逐行读懂再动手,那会陷入“一行都看不懂”的挫败感里。你只需要抓住主链路涉及的那二三十个类,就足够应付答辩。真正让老师觉得你有独立工作能力的,不是代码数量,而是你能讲清楚一个需求从“前端点击”到“数据库变更”的完整路径。

6.2 二次开发方向与答辩亮点设计

如果你的时间还有富余,我建议给项目增加两个小而美的功能亮点。第一个亮点是活动到期自动下架。用 Spring 的定时任务加@Scheduled,每分钟扫描一次活动表,把已过结束时间的活动状态改成“已结束”。这个功能代码量不大,但能体现你有“系统状态流转”的意识。第二个亮点是领券结果的异步通知,或者核销记录的 Excel 导出。导出功能用 EasyExcel 能很快实现,答辩时现场演示给老师导出一份核销报表,视觉效果比看代码强得多。

我个人做这类项目有一个习惯,就是每写完一个模块,就在项目根目录的 README 里记一笔“这个模块为什么这么设计”。等到写毕业论文的时候,这些记录就是现成的技术原理素材,完全不会再头疼“技术选型依据”那部分怎么写。技术方案没有绝对的对错,但能不能说出你选择它的理由,才是答辩打分的分水岭。希望你能在这个项目上不只拿到一份能跑的源码,更能真正理解它背后的设计逻辑,用你自己的方式把它讲清楚。

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

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

立即咨询