资讯动态

Spring Boot+微信小程序商家优惠活动系统源码解析与部署指南

发布时间:2026/10/3 3:09:59 来源:尧图企业网站定制
Spring Boot 搭配微信小程序做“商家优惠活动”这套路在毕业设计里太常见了但常见不等于容易。很多同学拿到一份源码打开 IDEA 直接 run结果不是 Redis 连不上就是小程序白屏最后忙着改 bug 的时间比写代码还多。项目标题里那串“计算机毕业设计源码 97781”指的其实是一套完整的前后端分离项目后端用 Spring Boot 提供接口前端是微信原生小程序围绕“商家发布优惠活动、用户浏览领券、到店核销”这条链路把业务串起来。今天我不讲虚的直接从源码角度把这个项目拆开把业务模型、核心接口、部署步骤和答辩前必须处理的坑一次说清。这套项目特别适合两类人一类是正在做毕设、需要尽快跑通并改成自己作品的学生另一类是刚接触小程序和 Spring Boot想找个完整案例练手的前端或后端初学者。看完你至少能回答三个问题这个系统解决什么问题、代码是怎么组织的、跑起来要跨过哪些坎。1. 项目拆解商家优惠活动小程序到底在做什么1.1 业务模型一条从发券到核销的闭环链路先把业务想清楚。一个优惠活动系统表面看是“商家发券、用户领券”两个动作实际拆开有四个角色和一条完整链路。参与角色有三个平台方通常就是管理员、商家、普通用户。平台方负责审核商家和活动保证发出去的不是虚假优惠商家创建活动、维护门店信息、查看核销数据用户在小程序里发现附近或热门的优惠活动领取后到店消费时出示核销码商家验证通过后完成核销。业务链路是这样的商家提交优惠活动比如“满 100 减 30”“第二杯半价”→ 管理员审核上架 → 用户在小程序首页刷到活动 → 用户领取优惠券/参与资格 → 用户到店出示凭证 → 商家核销 → 系统记录核销流水并提供统计。举一个真实场景一家奶茶店注册入驻后在商家端创建一个“买一送一”活动设置活动时间、可领取数量、适用门店。平台审核通过后小程序首页和商家详情页就能看到这个活动。用户在微信里一键领取到店后店员用手机号或核销码验证确认无误后点核销活动库存减一。用户手里“待使用”的券变成“已核销”商家后台看到一笔有效核销记录。整条链路只要有一个环节设计不合理体验就会崩。所以项目里最核心的不是页面 UI而是“库存控制”“核销校验”“状态流转”这三件事。1.2 技术选型为什么这么搭这套项目的技术栈选择很有代表性Spring Boot MyBatis Plus MySQL Redis MinIO 微信小程序原生。为什么用 Spring Boot因为它把配置繁琐的 Spring 生态做了大量简化内置 Tomcat打包成 JAR 就能跑对毕设来说学习成本低、部署方便而且网上资料和面试考点都多。Spring Boot 2.x 版本在多数毕设里还是主流如果源码用的是 2.7.xJDK 用 8 或 11 都没问题如果源码直接上了 Spring Boot 3.x那必须配 JDK 17这一点要提前确认不然启动就报错。MyBatis Plus 是 MyBatis 的增强工具最实用的就是不用写太多 XML单表 CRUD 靠 BaseMapper 接口直接完成分页用 IPage 加拦截器就能搞定。对毕设这种“基础增删改查占大头”的项目来说开发效率比纯 MyBatis 高不少答辩时也容易讲清楚。Redis 在这个项目里承担了两件重要的事一是保存小程序登录态的 token二是做优惠券库存的缓存和防超发控制。用户的每一次 wx.login 都会换来一个自定义 token存在 Redis 里并设置过期时间用户领取优惠券时先查 Redis 里的库存再用原子操作扣减避免高并发下超发。MinIO 是开源的分布式对象存储服务用来存商家头像、活动图片这类文件。它兼容 S3 API本地部署非常轻量比直接用服务器磁盘更规范也比接阿里云 OSS 省钱。源码里如果看到了 minio 相关配置需要在本地装一个 MinIO 服务创建好 bucket并把 endpoint、accessKey、secretKey 配到 application.yml 里。微信小程序端用的是原生语法就是 WXML WXSS JS 那一套。虽然现在很多人用 uni-app 跨端打包但毕设源码里原生小程序更常见因为不需要额外编译链路微信开发者工具直接导入就能跑真机预览也方便。1.3 源码目录结构解读拿到源码后第一件事不是急着启动而是先看目录结构。一个规范的后端项目通常长这样springboot-merchant-promotion/ ├── src/main/java/com/example/promotion/ │ ├── config/ # 配置类MyBatis Plus、Redis、MinIO、跨域 │ ├── controller/ # 接口层ActivityController、CouponController、AuthController 等 │ ├── service/ # 业务层接口 实现 │ │ └── impl/ │ ├── mapper/ # MyBatis Plus 的 Mapper 接口 │ ├── entity/ # 数据库实体类 │ ├── dto/ # 请求和响应对象 │ ├── common/ # 统一返回结果、异常处理、工具类 │ └── PromotionApplication.java # 启动类 ├── src/main/resources/ │ ├── application.yml # 核心配置 │ ├── mapper/ # 复杂 SQL 的 XML 文件 │ └── sql/ # 数据库初始化脚本小程序端是另一个目录miniprogram/ ├── pages/ │ ├── index/ # 首页活动列表、搜索、加载更多 │ ├── merchant/ # 商家详情页 │ ├── activity/ # 活动详情与领券 │ ├── coupon/ # 我的卡券列表 │ └── mine/ # 个人中心 ├── utils/ # 请求封装、工具函数 ├── app.js # 全局逻辑启动时登录、全局 baseUrl ├── app.json # 小程序页面路由和窗口配置 └── project.config.json # 项目配置看源码时建议按“启动类 → 配置类 → entity → mapper → service → controller”的顺序读先搞清楚数据模型再理解接口逻辑。不要一上来就盯前端前端页面再好看接口不通照样白搭。2. 核心功能拆解从领券到核销的每一环2.1 数据库模型与字段设计数据库是这个项目的地基。商家优惠活动这类系统核心表至少有五张。第一张是商家表字段包括商家 ID、商家名称、联系人手机号、头像、门店地址、经纬度、营业时间、入驻时间、状态等。经纬度字段很关键因为小程序端可以做“附近商家”功能按距离排序虽然很多源码里只是存了字段没真正实现 LBS 计算但答辩时可以提。第二张是优惠活动表这是业务中心。常见字段有活动 ID、商家 ID、活动标题、活动类型满减/折扣/买赠/秒杀、优惠力度、活动开始时间、活动结束时间、发放总量、已领取数量、封面图、活动详情描述、状态待审核/已上架/已下架/已结束。状态字段最好用整数枚举而不是字符串否则查询和扩展都不方便。第三张是用户优惠券表也叫领取记录表。每个用户每领一张券就生成一条记录字段包括记录 ID、用户 ID、活动 ID、券码或核销码、状态未使用/已核销/已过期、领取时间、核销时间、核销商家 ID。这张表是最终对账和统计的依据也是判断“有没有超发”的凭证。第四张是核销记录表记录每一次核销流水核销 ID、优惠券记录 ID、商家 ID、核销时间、核销方式。有了这张表商家维度的日核销数、周核销数就能统计出来。第五张是用户表保存微信用户的信息用户 ID、OpenID、昵称、头像、手机号、注册时间。注意这里不应该存微信号和明文密码OpenID 才是微信体系下的唯一标识。我在阅读源码时通常会先看 SQL 脚本里的表结构重点看字段注释是否完整、有没有唯一索引。比如“用户优惠券表”里应该为 (user_id, activity_id) 建唯一索引从数据库层面防止同一用户重复领取同一个活动这是很实用的小细节。2.2 后端接口设计接口设计直接决定前端好不好写。一个优惠活动小程序核心接口大概有这些模块方法路径说明认证POST/api/auth/login微信登录code 换 token活动GET/api/activity/page活动分页列表支持关键词和分类活动GET/api/activity/detail活动详情供详情页展示商家GET/api/merchant/detail商家详情与门店信息领券POST/api/coupon/receive用户领取优惠券卡券GET/api/coupon/myList我的卡券列表按状态筛选核销POST/api/verify/confirm核销员验证并核销统计GET/api/merchant/stats商家端核销统计分页列表接口是最常见也最容易出问题的地方。小程序端“加载更多”的实现方式本质上就是页码累加。前端传 current 和 size后端返回 total 和 records前端每次触底时 current1 再请求一次把新数据追加到列表尾部。核心代码如下GetMapping(/page) public ResultIPageActivityVO page(RequestParam(defaultValue 1) long current, RequestParam(defaultValue 10) long size, String keyword, Integer type) { LambdaQueryWrapperPromotionActivity wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), PromotionActivity::getTitle, keyword) .eq(type ! null, PromotionActivity::getType, type) .eq(PromotionActivity::getStatus, 1) .orderByDesc(PromotionActivity::getCreateTime); IPagePromotionActivity page activityMapper.selectPage(new Page(current, size), wrapper); // 转换 VO补充商家名称、封面地址等冗余字段 return Result.success(convertToVO(page)); }这里有一个容易踩的坑如果用 MyBatis Plus 的分页一定要在配置类里注册 PaginationInnerInterceptor否则分页查询会查全表前端“加载更多”只能翻出总记录数接口却把全部数据一次性返回。很多源码跑起来列表卡顿问题就出在这。领取优惠券接口是另一个重点。用户点击“立即领取”后后端要做的不是简单地 insert 一条记录而是先判断活动状态、是否在有效期内、库存是否充足再检查用户是否已经领过。为了防止超发推荐用 Redis 预扣库存public ResultString receive(Long userId, Long activityId) { String stockKey promotion:stock: activityId; String userKey promotion:user: userId : activityId; // 1. 判断活动是否在有效期内 PromotionActivity activity activityMapper.selectById(activityId); if (activity null || activity.getStatus() ! 1) { return Result.failed(活动不存在或已下架); } // 2. 判断用户是否已领取Redis setnx Boolean first stringRedisTemplate.opsForValue() .setIfAbsent(userKey, 1, Duration.ofHours(24)); if (Boolean.FALSE.equals(first)) { return Result.failed(您已领取过该活动优惠); } // 3. 原子扣减库存 Long remain stringRedisTemplate.opsForValue() .increment(stockKey, -1); if (remain null || remain 0) { // 回滚用户标记 stringRedisTemplate.delete(userKey); return Result.failed(手慢了优惠已被抢光); } // 4. 写领取记录…… return Result.success(领取成功); }这里用到了 Redis 的 setIfAbsent 原子方法和 increment 自减操作比“先查再减”的方式靠谱得多。毕设答辩时老师很可能会问“高并发下如何防止超发”能答出 Redis 原子操作加唯一索引双重保障就已经超出大多数人了。核销接口则要处理“伪造”问题。用户出示的核销码通常是服务端生成的一串随机字符串核销时先查这条券码的状态确保是“未使用”再更新为“已核销”。如果做的更完善核销码可以做成一次性短码使用后立即作废并且记录核销人、核销门店和时间。2.3 小程序端页面与交互小程序端虽然代码不难但“微信登录”是绕不开的一道坎。登录流程是小程序端调用 wx.login 拿到临时 code把 code 传给后端后端用 code 调用微信的 jscode2session 接口换取 openid 和 session_key拿到 openid 后如果数据库里没有这个用户就自动注册最后生成一个自定义 token 返回给小程序之后所有请求都在 header 里带这个 token。// 小程序端 app.js wx.login({ success: (res) { wx.request({ url: ${baseUrl}/api/auth/login, method: POST, data: { code: res.code }, success: (response) { const token response.data.data.token; wx.setStorageSync(token, token); } }); } });PostMapping(/login) public ResultLoginVO login(RequestBody LoginDTO dto) { String url https://api.weixin.qq.com/sns/jscode2session ?appid appid secret secret js_code dto.getCode() grant_typeauthorization_code; String result restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(result); String openid json.getString(openid); User user userMapper.selectOne(new LambdaQueryWrapperUser() .eq(User::getOpenid, openid)); if (user null) { user new User(); user.setOpenid(openid); userMapper.insert(user); } String token UUID.randomUUID().toString().replace(-, ); stringRedisTemplate.opsForValue() .set(login:token: token, openid, Duration.ofDays(7)); return Result.success(new LoginVO(token)); }注意jscode2session 接口返回的 openid 是核心session_key 不要存储在数据库里更不要返回给前端。如果源码里直接把 session_key 落库属于设计缺陷答辩时会被追问。页面层面的实现重点有两个。第一个是首页“加载更多”的触底逻辑用的是小程序原生 Page 里的 onReachBottom 方法每次触底判断还能不能继续加载不能则提示“已经到底了”。第二个是优惠券列表的状态筛选用 tab 切换“未使用 / 已使用 / 已过期”本质上就是给同一个接口传不同 status 参数然后重新请求第一页数据。3. 部署与联调把源码跑起来的完整流程3.1 环境准备与配置毕设源码跑不起来十有八九是环境不一致。先把所需的工具列全再逐个核对版本。JDK8 或 11 对应 Spring Boot 2.x17 对应 Spring Boot 3.xMaven3.6用来拉取依赖MySQL5.7 或 8.0注意 8.0 后驱动类名变成了 com.mysql.cj.jdbc.DriverRedisWindows 下建议用 Redis-x64 版本或 WSL/DockermacOS 下用 brew install redisMinIO官网下载服务端本地启动创建 bucket 并设置公开读权限微信开发者工具导入小程序目录需注册一个小程序账号获取 AppID拿到源码后千万别直接跑。第一步是先打开 application.yml把数据库账号密码、Redis 地址、MinIO 地址、微信小程序 AppID 和 Secret 全部替换成自己的。很多毕设源码会把敏感配置留空或用占位符代替比如 appid: your_appid不替换的话登录接口必挂。3.2 后端配置要点一个典型的 application.yml 配置长这样server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/promotion_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 mybatis-plus: mapper-locations: classpath*:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl minio: endpoint: http://localhost:9000 access-key: minioadmin secret-key: minioadmin bucket: promotion-images wechat: appid: your_appid secret: your_secret这里有几个细节要特别注意。数据库连接串里的 serverTimezone 必须配否则 MySQL 8 下日期类型会报错。Redis 如果设置了密码还要补上 password 字段。MinIO 默认账号密码都是 minioadmin如果本地没改过就填默认值。SQL 初始化脚本一般放在 resources/sql 目录下用 Navicat 或命令行导入即可。导入后建议先手动执行几条 SELECT确认表结构和预期一致。如果源码自带的 SQL 里有测试数据更好前端页面不至于空荡荡的。3.3 小程序端配置与真机预览小程序端配置集中在 app.js 或一个独立的 config.js 文件里。需要修改两处第一是 baseUrl本地联调时用http://localhost:8080第二是 AppID在project.config.json里替换成自己的。本地开发时微信开发者工具默认会拦截不合法的域名解决办法有两个。一是在工具右上角“详情 → 本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”二是如果要做真机预览需要把后端接口部署到公网并在小程序后台配置 request 合法域名而且必须是 HTTPS。对毕设来说通常用前者在开发者工具里演示就足够了。真正要演示到手机上的话推荐两种方式一是用开发者工具的“真机调试”功能手机会自动打开调试版小程序请求可以走本地 localhost 加端口映射二是把小程序上传为体验版后端部署到云服务器手机扫码进入体验版访问。体验版最接近真实效果但要求后端有公网地址且小程序后台把请求域名配好。我在帮别人调这类项目时遇到的最多问题是“开发者工具预览白屏”。排查思路很简单先看 Console 有没有报错再点开 Network 看第一个请求有没有发出如果请求都没发出大概率是 baseUrl 没有生效或者工具里没开“不校验合法域名”。把这三步走完90% 的前端问题都能定位。4. 毕设答辩与自检清单别让源码害了你4.1 运行前必查的五个坑这些坑我几乎每次都会碰到直接列成清单按顺序检查能省大量时间。第一端口冲突。如果本地 8080 被占用后端会启动失败或一直转圈。解决办法是改 application.yml 里的 server.port或者找到占用进程关掉。因为毕设项目通常没有前端工程化管理后台后端启动成功后会输出类似Tomcat started on port(s): 8080的日志看到这行才算真起来了。第二MySQL 驱动问题。如果用的是 MySQL 8.x但配置里还写着旧驱动类com.mysql.jdbc.Driver连接会直接报 ClassNotFoundException。新版应该是com.mysql.cj.jdbc.Driver。这一条在 Spring Boot 2.7 和 3.x 之间表现还不同最好根据自己本地数据库版本来配置。第三Redis 没启动。很多源码把登录态、库存都压在 Redis 上Redis 挂了系统不会直接报错但登录、领券全部异常。启动 Redis 后可以用redis-cli ping确认返回 PONG。第四MinIO 桶权限。如果系统上传图片后无法显示多半是桶的访问策略没设为 public。本地装好 MinIO 后在浏览器打开控制台把 bucket 的 Access Policy 改成 Public图片 URL 才能匿名访问。第五小程序 AppID 问题。用测试号或未认证的小程序某些能力会受限比如获取手机号、订阅消息。如果源码里用到了这些功能演示前必须确认账号权限。基础登录和页面展示不受影响但用别人的 AppID 会出现“AppID 不匹配”的提示。4.2 答辩高频问题整理答辩时老师一般不会对整个项目逐行解读而是挑几个核心点问。结合这个题目我总结了一套高频问题。“为什么要用 Spring Boot 而不是 SSM”答Spring Boot 简化了配置内置服务器适合快速开发 RESTful 接口而且生态成熟和 MyBatis Plus、Redis 整合都方便。“RESTful 接口是什么你们是怎么设计的”答以资源为中心用 HTTP 方法表示操作。项目的活动列表是 GET /api/activity/page领取优惠券是 POST /api/coupon/receive核销是 POST /api/verify/confirm每个接口职责单一返回统一格式的 JSON。“微信小程序的登录流程是什么”把前面那套 wx.login → code → jscode2session → openid → 自定义 token 的过程讲清楚即可。这里特别强调 token 为什么存 Redis设置过期时间、分布式环境下天然共享、服务重启不丢数据。“领券时怎么防止超发”这是一个好问题。我的回答分三层数据库唯一索引防重复领取Redis 原子扣减防超发活动状态检查防领取已结束的活动。如果能说出“在极端并发下Redis 扣减后应该异步同步数据库库存”这句话老师会高看你一眼。“核销码如何防伪造”答核销码是服务端生成的随机短码不是简单的 ID 自增用户无法枚举核销时后端校验状态已核销的码不能二次使用核销流水留痕出现异常可追溯。4.3 给源码二次开发做出自己的作品直接拿现成源码交毕设是大忌哪怕功能再完整老师随便问一个细节你就露馅。更聪明的方法是“保留骨架替换血肉”至少做三处改动。第一全局搜索替换包名。比如把com.example.promotion换成自己名字缩写或学号相关内容避免和网上原版撞车。第二修改数据库表前缀。原版表叫activity你可以改成myb_activity这样的命名字段结构不变但一眼看过去就不一样。第三更换 UI 主色调和文案。小程序端 WXSS 里找主题色或者把首页轮播图、活动封面全部替换成自己的素材。更推荐的是加一个独立功能哪怕不复杂也能成为答辩亮点。比如给“我的卡券”页加一个“使用地图导航到店”按钮调微信原生wx.openLocation或者给商家端加一个“核销统计”接口返回今日核销数、累计核销数和优惠券核销率。这些功能在原项目上改动不大但可以讲出一段完整的“我为了解决什么问题、做了哪些设计”的故事。5. 扩展方向如何把项目做成真正的加分项5.1 用定时任务实现活动自动上下架原项目里的活动上下架很可能依赖管理员手工操作但真实场景应该按时自动开始、自动结束。用 Spring 自带的Scheduled注解每五分钟扫描一次活动表把开始时间小于当前时间且状态为待开始的改成进行中把结束时间小于当前时间的改成已结束。代码量不大但业务完成度立刻提高一个档次。5.2 给核销码加一点“安全感”常见的核销码是用 UUID 截断生成的但 UUID 可能很长、不好输入。可以改成 8 位短码先用 UUID 或随机数生成再在 Redis 里存一份映射核销时从 Redis 查。更安全的做法是生成时校验不重复或者用 Base32 编码的方式缩短长度。尽管这个系统没有支付场景但能体现出你有“安全设计”的意识。5.3 增加数据可视化面板商家端如果只有一个表格不够直观。可以在小程序“我的”页面里加一个简单的统计入口点击后展示近七天的核销趋势。小程序端可以用 ec-canvas 组件接入 ECharts 画柱状图后端提供一个接口按日期分组返回核销数量。这一块的难度主要在 SQL 的日期函数属于工作量小、展示效果好、答辩能讲出内容的部分。5.4 用订阅消息做活动提醒微信小程序支持订阅消息用户点击“开售提醒”后授权一次活动即将开始前可以收到一条服务通知。这个功能需要在小程序后台申请消息模板后端配合定时任务或 Redis 延迟队列来实现。作为扩展方向它比普通 CRUD 更有记忆点。最后再分享一个我做毕设项目时的习惯所有配置、SQL、小程序的 AppID 信息一定要集中写在项目根目录的 README.md 里并配上启动步骤截图。因为答辩前的环境往往是临时搭建的你又紧张又忙没有文档只能靠脑子回忆很容易漏配置导致现场翻车。把部署过程整理成文档也是答辩时主动展示的一个亮点证明你真正动手跑过这个项目而不是只看过源码。整个项目跑通之后建议把文档里提到的五张表、四类接口、两套状态流转完整过一遍心里有数答辩就不会慌。

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

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

免费获取报价 →
↑