Spring Boot 3 + 微信云开发:点餐系统源码实战搭建指南
引言
Spring Boot 3 作为 Java 后端的绝对主力,凭借 JDK 17+ 的原生支持、AOT 编译能力和 GraalVM 原生镜像,启动速度比 2.x 快了近 40%。而 微信云开发 则让小程序前端彻底摆脱了"自己搭服务器"的噩梦——云函数、云数据库、云存储三位一体,开发效率直接翻倍。这套组合拳打出来的点餐系统,后端扛并发,前端零运维,堪称 2026 年中小餐饮数字化的最优解。本文将从零开始,手把手带你搭建一套完整的点餐系统,包含源码级的核心实现。
源码及演示:s.ymzan.top
系统架构设计
整体采用 前后端分离 + 云开发混合架构:
| 层级 | 技术选型 | 职责 |
|---|---|---|
| 前端(用户端) | 微信小程序 + 云开发 | 菜品浏览、下单、支付、订单查询 |
| 前端(管理端) | Vue 3 + Element Plus | 菜品管理、订单管理、数据统计 |
| 后端服务 | Spring Boot 3 + MyBatis-Plus | 业务逻辑、权限控制、支付回调 |
| 数据库 | MySQL 8.0 | 持久化存储 |
| 缓存 | Redis 7.x | 热点数据缓存、分布式锁、Session |
| 消息队列 | 可选 RabbitMQ | 订单异步处理、削峰填谷 |
核心数据流:
用户小程序 → 微信云函数(鉴权/轻量逻辑)→ Spring Boot 3(核心业务)→ MySQL/Redis
为什么不全用云开发?因为涉及支付回调、复杂订单状态机、多端管理后台这些重逻辑,云函数的执行时长和灵活性都不够。Spring Boot 3 负责"重活",云开发负责"快活",各司其职。
环境搭建与项目初始化
1. 开发环境
| 工具 | 版本要求 |
|---|---|
| JDK | 17+(Spring Boot 3 最低要求) |
| Maven | 3.6+ |
| MySQL | 8.0.17+ |
| Redis | 7.x |
| Node.js | 16+(小程序开发) |
| 微信开发者工具 | 最新稳定版 |
2. Spring Boot 3 项目初始化
使用 Spring Initializr(start.spring.io)创建项目,添加以下依赖:
<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- 微信 SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>4.5.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
</dependencies>
3. application.yml 核心配置
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/ordering_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: localhost
port: 6379
password:
database: 0
wechat:
mp:
app-id: your_app_id
secret: your_secret
jwt:
secret: your-256-bit-secret-key-here
expiration: 86400000 # 24小时
后端核心实现(Spring Boot 3)
1. 项目目录结构
com.example.ordering
├── config/ # Redis配置、安全配置、跨域配置
├── controller/ # API控制器
├── entity/ # 数据库实体
├── mapper/ # MyBatis-Plus Mapper
├── service/ # 业务逻辑层
├── dto/ # 数据传输对象
├── util/ # 工具类(JWT、分布式锁等)
└── exception/ # 全局异常处理
2. 数据库设计
这是整个系统的地基,设计不好后面全是坑。
菜品信息表(dish_info)
| 字段 | 类型 | 说明 |
|---|---|---|
| dish_id | VARCHAR(32) | 主键 |
| dish_name | VARCHAR(50) | 菜品名称 |
| price | DECIMAL(10,2) | 价格 |
| category | VARCHAR(20) | 分类 |
| image_url | VARCHAR(255) | 图片链接 |
| description | TEXT | 描述 |
| status | TINYINT(1) | 1上架/0下架 |
| create_time | DATETIME | 创建时间 |
订单信息表(order_info)
| 字段 | 类型 | 说明 |
|---|---|---|
| order_id | VARCHAR(32) | 主键 |
| user_id | VARCHAR(32) | 用户ID |
| dish_list | TEXT | 菜品列表(JSON) |
| total_amount | DECIMAL(10,2) | 总金额 |
| payment_status | TINYINT(1) | 1已支付/0未支付 |
| order_status | TINYINT(1) | 0待付款/1已付款/2制作中/3已完成/4已取消 |
| create_time | DATETIME | 创建时间 |
| finish_time | DATETIME | 完成时间 |
用户表(user)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键自增 |
| openid | VARCHAR(64) | 微信OpenID |
| nickname | VARCHAR(32) | 昵称 |
| phone | VARCHAR(11) | 手机号 |
| create_time | DATETIME | 注册时间 |
3. 微信登录——整个系统的入口
微信登录是点餐系统的第一道门。2024年微信官方政策已明确:新注册公众号需完成企业认证才能获得接口权限,个人订阅号接口能力大幅受限。所以务必使用已认证的服务号。
核心流程:
小程序端 wx.login() → 获取 code → 后端调用 auth.code2Session → 获取 openid + session_key → 生成 JWT token → 返回给前端
SignUtil.java — 签名验证工具
@Component
public class SignUtil {
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[]{"your_token", timestamp, nonce};
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (String s : arr) {
content.append(s);
}
String tmpStr = DigestUtils.sha1Hex(content.toString());
return tmpStr != null && tmpStr.equals(signature);
}
}
WeChatController.java — 微信接入验证
@RestController
@RequestMapping("/wechat")
public class WeChatController {
@Autowired
private WeChatService weChatService;
@GetMapping("/message")
public String validate(@RequestParam String signature,
@RequestParam String timestamp,
@RequestParam String nonce,
@RequestParam String echostr) {
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
return echostr;
}
return "error";
}
@PostMapping("/login")
public Result login(@RequestBody LoginDTO dto) {
WxMaJscode2SessionResult session = weChatService.getSessionInfo(dto.getCode());
String openid = session.getOpenid();
String token = weChatService.loginOrRegister(openid);
return Result.success(Map.of("token", token, "userInfo", weChatService.getUserInfo(openid)));
}
}
JWT 工具类
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
public String generateToken(Long userId, String username) {
return Jwts.builder()
.subject(username)
.claim("userId", userId)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(Keys.hmacShaKeyFor(secret.getBytes()))
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.verifyWith(Keys.hmacShaKeyFor(secret.getBytes()))
.build()
.parseSignedClaims(token)
.getPayload();
}
}
4. 菜品管理模块
DishController.java
@RestController
@RequestMapping("/api/dish")
public class DishController {
@Autowired
private DishService dishService;
@GetMapping("/list")
public Result list(@RequestParam(required = false) String category) {
return Result.success(dishService.listByCategory(category));
}
@GetMapping("/{id}")
public Result detail(@PathVariable String id) {
return Result.success(dishService.getById(id));
}
@PostMapping("/admin/add")
@PreAuthorize("hasRole('ADMIN')")
public Result add(@RequestBody DishDTO dto) {
dishService.save(dto);
return Result.success("添加成功");
}
}
DishService.java 核心逻辑
@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public List<Dish> listByCategory(String category) {
String cacheKey = "dish:list:" + (category != null ? category : "all");
// 先查缓存
List<Dish> cached = (List<Dish>) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// 缓存未命中,查数据库
LambdaQueryWrapper<Dish> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Dish::getStatus, 1);
if (StringUtils.isNotBlank(category)) {
wrapper.eq(Dish::getCategory, category);
}
List<Dish> list = this.list(wrapper);
// 写入缓存,30分钟过期
redisTemplate.opsForValue().set(cacheKey, list, 30, TimeUnit.MINUTES);
return list;
}
}
这里用 Redis 缓存了菜品列表,这是点餐系统的性能命脉——高峰期每秒几百次的菜品查询,全部打到 MySQL 上直接宕机。
5. 订单模块——最复杂也最核心
订单创建涉及三个关键问题:库存扣减的并发安全、支付回调的幂等性、订单状态机的流转控制。
5.1 分布式锁防止超卖
这是点餐系统的技术难点。多个用户同时点同一道菜,库存只剩 1 份,必须保证只有一个人能下单成功。
错误写法一:先 setnx 再 expire(有死锁风险)
Long result = jedis.setnx(key, value);
if (result == 1) {
jedis.expire(key, expireTime); // 如果这里程序崩溃,锁永远不过期 → 死锁
}
正确写法:原子命令 setnx + 过期时间一步到位(Spring Boot 3 + Redis)
@Component
public class RedisLockUtil {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean tryLock(String key, String value, long expireSeconds) {
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
public void unlock(String key, String value) {
String script = "if redis.call('get',KEYS[1])==ARGV[1] then " +
"return redis.call('del',KEYS[1]) else return 0 end";
redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(key),
value
);
}
}
OrderService.java — 下单核心逻辑
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RedisLockUtil redisLockUtil;
@Autowired
private DishMapper dishMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrder(CreateOrderDTO dto) {
String lockKey = "dish:stock:" + dto.getDishId();
String lockValue = UUID.randomUUID().toString();
try {
// 获取分布式锁,最多等待3秒,锁自动10秒后释放
if (!redisLockUtil.tryLock(lockKey, lockValue, 10)) {
throw new BusinessException("系统繁忙,请稍后重试");
}
// 查库存
Dish dish = dishMapper.selectById(dto.getDishId());
if (dish == null || dish.getStock() < dto.getQuantity()) {
throw new BusinessException("库存不足");
}
// 扣库存
dish.setStock(dish.getStock() - dto.getQuantity());
dishMapper.updateById(dish);
// 生成订单
Order order = new Order();
order.setOrderId(IdUtil.fastSimpleUUID());
order.setUserId(dto.getUserId());
order.setDishList(JSON.toJSONString(dto.getDishList()));
order.setTotalAmount(dto.getTotalAmount());
order.setPaymentStatus(0);
order.setOrderStatus(0);
order.setCreateTime(new Date());
this.save(order);
return order.getOrderId();
} finally {
redisLockUtil.unlock(lockKey, lockValue);
}
}
}
这段代码的关键在于:锁的粒度是每道菜,而不是整个订单表。这样不同菜品之间的下单互不影响,并发能力直接拉满。
5.2 支付回调幂等处理
微信支付回调会发多次通知,必须保证同一笔订单只处理一次。
@PostMapping("/pay/notify")
public String payNotify(@RequestBody String xmlData) {
try {
Map<String, String> map = WxPayUtil.xmlToMap(xmlData);
String orderId = map.get("out_trade_no");
// 幂等检查:先查订单状态
Order order = this.getById(orderId);
if (order.getPaymentStatus() == 1) {
return WxPayUtil.successXml(); // 已经处理过,直接返回成功
}
// 验证金额
if (!order.getTotalAmount().equals(new BigDecimal(map.get("total_fee")).divide(new BigDecimal(100)))) {
throw new BusinessException("金额不一致");
}
// 更新订单状态
order.setPaymentStatus(1);
order.setOrderStatus(1);
order.setFinishTime(new Date());
this.updateById(order);
return WxPayUtil.successXml();
} catch (Exception e) {
log.error("支付回调处理失败", e);
return WxPayUtil.failXml();
}
}
6. 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public Result handleBusiness(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
log.error("系统异常", e);
return Result.error(500, "系统繁忙,请稍后重试");
}
}
前端实现:微信小程序 + 云开发
1. 为什么用云开发?
对于点餐系统的前端,80% 的需求是"查菜品、下订单、查订单",这些都是 CRUD 操作,云开发的云数据库 + 云函数天然适配。
2.项目结构
miniprogram/
├── app.js
├── app.json
├── app.wxss
├── pages/
│ ├── index/ # 首页(菜品列表)
│ ├── dish/ # 菜品详情
│ ├── order/ # 订单确认
│ ├── orderList/ # 订单列表
│ └── mine/ # 个人中心
├── cloud/ # 云函数
│ ├── login/ # 微信登录
│ └── createOrder/ # 创建订单(轻量逻辑)
└── utils/
└── request.js # 封装请求
3. app.json 配置
{
"pages": [
"pages/index/index",
"pages/dish/dish",
"pages/order/order",
"pages/orderList/orderList",
"pages/mine/mine"
],
"window": {
"navigationBarBackgroundColor": "#07c160",
"navigationBarTitleText": "美味点餐"
},
"cloud": true,
"subpackages": [
{
"root": "pages/order",
"pages": ["confirm/confirm"]
}
]
}
4. 首页——菜品列表(云数据库查询)
// pages/index/index.js
const app = getApp()
const db = wx.cloud.database()
Page({
data: {
categories: ['热销', '主食', '小炒', '凉菜', '饮品'],
currentCategory: '热销',
dishes: []
},
onLoad() {
this.loadDishes()
},
async loadDishes() {
wx.showLoading({ title: '加载中' })
try {
const res = await db.collection('dishes')
.where({ status: 1, category: this.data.currentCategory })
.orderBy('sales', 'desc')
.limit(20)
.get()
this.setData({ dishes: res.data })
} catch (e) {
console.error(e)
}
wx.hideLoading()
},
switchCategory(e) {
this.setData({ currentCategory: e.currentTarget.dataset.cat })
this.loadDishes()
}
})
5. 下单——调用 Spring Boot 后端
// pages/order/order.js
const app = getApp()
Page({
data: {
cart: [],
totalAmount: 0
},
async submitOrder() {
if (this.data.cart.length === 0) {
wx.showToast({ title: '请先选菜', icon: 'none' })
return
}
wx.showLoading({ title: '提交订单' })
try {
const res = await wx.cloud.callFunction({
name: 'createOrder',
data: {
dishList: this.data.cart,
totalAmount: this.data.totalAmount
}
})
if (res.result.code === 0) {
// 调起微信支付
const payParams = res.result.payParams
wx.requestPayment({
...payParams,
success() {
wx.showToast({ title: '下单成功' })
wx.redirectTo({ url: '/pages/orderList/orderList' })
}
})
}
} catch (e) {
wx.showToast({ title: '下单失败', icon: 'none' })
}
wx.hideLoading()
}
})
cloud/createOrder/index.js — 云函数
const cloud = require('wx-server-sdk')
const axios = require('axios')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
exports.main = async (event) => {
const { dishList, totalAmount } = event
const wxContext = cloud.getWXContext()
const openid = wxContext.OPENID
// 调用 Spring Boot 后端创建订单
const res = await axios.post('https://your-domain.com/api/order/create', {
userId: openid,
dishList: dishList,
totalAmount: totalAmount
})
if (res.data.code === 0) {
// 获取支付参数
const payRes = await axios.post('https://your-domain.com/api/pay/create', {
orderId: res.data.data.orderId,
openid: openid,
amount: totalAmount
})
return { code: 0, payParams: payRes.data.data }
}
return { code: -1, message: '创建订单失败' }
}
部署与性能优化
1. Docker 部署
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/ordering-system.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: your_password
MYSQL_DATABASE: ordering_system
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
mysql_data:
2. 关键优化点
| 优化项 | 方案 | 效果 |
|---|---|---|
| 菜品列表缓存 | Redis 30分钟过期 | QPS 提升 10 倍 |
| 下单并发控制 | Redis 分布式锁(按菜品粒度) | 杜绝超卖 |
| 支付回调幂等 | 先查状态再处理 | 避免重复入账 |
| 小程序分包 | 主包 < 2MB,分包异步加载 | 首屏加载 < 1s |
| 图片存储 | 阿里云 OSS + CDN | 图片加载 < 200ms |

总结
Spring Boot 3 解决了点餐系统最难啃的骨头——并发锁等、订单状态机,必须用成熟的 Java 生态兜底。微信云开发则把前端从"搭服务器、配域名、搞运维"的泥潭里解放出来,让开发者把精力花在用户体验上,而不是基础设施上。技术会迭代,但架构思想不会过时。2026年再看这套系统,它依然适用于奶茶店、快餐店、食堂等绝大多数中小餐饮场景。真正决定系统成败的,从来不是用了多新的框架,而是你有没有把并发、幂等、缓存这三件事做扎实。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)