点餐外卖源码全解析:小程序+APP双端融合,打造餐饮数字化新生态
在餐饮行业数字化转型的浪潮中,微信点餐外卖系统凭借其低门槛、高触达的特性成为核心载体。本文基于一套完整的开源源码,从架构设计、核心模块实现、双端融合策略三个维度展开深度解析,揭示如何通过源码构建覆盖小程序与APP双端的餐饮数字化生态。
一、系统架构设计:双端融合的技术底座
源码及演示:s.ymzan.top
系统采用前后端分离的微服务架构,前端基于微信小程序原生框架(WXML/WXSS/JavaScript)与跨端框架UniApp实现双端适配,后端采用Spring Cloud Gateway作为API网关,配合Dubbo实现服务治理,数据库层使用MySQL(分库分表存储订单数据)+ Redis(缓存热点数据)+ MongoDB(存储非结构化数据如评价图片)的混合存储方案。
关键技术选型逻辑:
前端跨端方案:UniApp通过Vue语法生成小程序与H5代码,配合条件编译适配APP原生特性(如Android的沉浸式导航栏)。例如,在
pages/menu/menu.vue中通过#ifdef APP-PLUS指令实现APP端特有的支付弹窗样式:
vue
APP支付
{{item.name}}¥{{item.price}}
后端服务拆分:用户服务、商品服务、订单服务独立部署,通过Dubbo进行RPC通信。例如订单服务中的下单逻辑:
java
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public Order placeOrder(Long userId, List dishIds) {
User user = userRepository.findById(userId).orElseThrow();
List dishes = dishRepository.findByIds(dishIds);
BigDecimal totalPrice = dishes.stream().map(Dish::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
Order order = new Order();
order.setUser(user);
order.setDishes(dishes);
order.setTotalPrice(totalPrice);
order.setStatus(OrderStatus.UNPAID);
return orderRepository.save(order);
}
}
数据同步策略:通过Redis分布式锁保证购物车并发安全。在
RedisLock类中实现锁的获取与释放:
java
public class RedisLock {
private final StringRedisTemplate stringRedisTemplate;
public boolean tryLock(String lockKey, long expireTime) {
String lockValue = UUID.randomUUID().toString();
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
public boolean releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Arrays.asList(lockKey), lockValue);
return result != null && result == 1;
}
}
二、核心功能源码实现:从点餐到配送的全流程
1. 扫码点餐与桌台绑定
在pages/scanOrder/scanOrder.js中实现扫码绑定桌台逻辑。用户扫描桌码后,小程序通过wx.scanCode获取桌台ID,调用后端API绑定订单与桌台:
javascript
Page({
data: {
tableId: null
},
onLoad(options) {
const tableId = options.tableId; // 从扫码参数获取
this.setData({ tableId });
this.bindTableToOrder(tableId);
},
bindTableToOrder(tableId) {
wx.request({
url: 'https://api.example.com/order/bindTable',
method: 'POST',
data: { tableId },
success: (res) => {
wx.showToast({ title: '桌台绑定成功' });
}
});
}
})
后端通过TableController生成桌台二维码:
java
@RestController
@RequestMapping("/api/table")
public class TableController {
@PostMapping("/generateQr")
public ResponseEntity generateQrCode(Long tableId) {
String qrUrl = "https://api.example.com/order?tableId=" + tableId;
// 实际使用ZXing库生成二维码图片
return ResponseEntity.ok(qrUrl);
}
}
2. 购物车与订单管理
购物车数据通过wx.setStorageSync实现本地持久化。在pages/cart/cart.js中实现购物车增删改查:
javascript
Page({
data: {
cartList: []
},
onLoad() {
const cart = wx.getStorageSync('cart') || [];
this.setData({ cartList: cart });
this.calcTotal();
},
addToCart(dish) {
const cart = this.data.cartList;
const index = cart.findIndex(item => item.id === dish.id);
if (index >= 0) {
cart[index].quantity++;
} else {
cart.push({ ...dish, quantity: 1 });
}
wx.setStorageSync('cart', cart);
this.setData({ cartList: cart });
this.calcTotal();
},
calcTotal() {
const total = this.data.cartList.reduce((sum, item) => sum + item.price * item.quantity, 0);
this.setData({ totalPrice: total });
}
})
订单提交时调用微信支付接口:
javascript
wx.requestPayment({
timeStamp: '',
nonceStr: '',
package: 'prepay_id=' + prepayId,
signType: 'MD5',
paySign: '',
success: () => {
wx.redirectTo({ url: '/pages/orderSuccess/orderSuccess' });
}
})
3. 商家后台管理
商家后台通过Web界面实现订单处理、菜品管理等功能。在admin/orderList.vue中展示订单列表并支持状态更新:
vue
<script>
export default {
data() {
return {
orders: []
};
},
mounted() {
this.fetchOrders();
},
methods: {
async fetchOrders() {
const res = await axios.get('/api/admin/orders');
this.orders = res.data;
},
async updateStatus(order) {
await axios.put(`/api/orders/${order.id}/status`, { status: order.status });
this.$message.success('状态更新成功');
}
}
};
script>
三、双端融合的关键技术实现
1. 状态管理与数据同步
使用Bloc模式统一管理全局状态。在store/cart/cart_bloc.dart中实现购物车状态管理:
dart
class CartBloc extends Bloc {
CartBloc() : super(CartInitial()) {
on((event, emit) {
final currentState = state is CartLoaded
? (state as CartLoaded)
: CartLoaded(cartItems: []);
final updatedItems = List.from(currentState.cartItems)
..add(event.item);
emit(CartLoaded(cartItems: updatedItems));
});
}
}
通过EventBus实现跨页面状态同步,在utils/eventBus.js中定义事件总线:
javascript
import Vue from 'vue';
export const eventBus = new Vue();
// 触发事件
eventBus.$emit('cartUpdated', cartItems);
// 监听事件
eventBus.$on('cartUpdated', (cartItems) => {
this.cartItems = cartItems;
});
2. 离线能力与数据持久化
APP端使用SQLite存储离线订单草稿:
javascript
// 使用cordova-sqlite-storage插件
const db = window.sqlitePlugin.openDatabase({ name: 'order.db' });
function saveDraftOrder(order) {
db.executeSql('INSERT INTO draft_orders (data) VALUES (?)', [JSON.stringify(order)]);
}
function getDraftOrders() {
return new Promise((resolve) => {
db.executeSql('SELECT * FROM draft_orders', [], (rs) => {
const orders = [];
for (let i = 0; i < rs.rows.length; i++) {
orders.push(JSON.parse(rs.rows.item(i).data));
}
resolve(orders);
});
});
}
小程序端使用wx.getStorageSync与wx.setStorageSync实现本地存储:
javascript
// 保存购物车
wx.setStorageSync('cart', cartData);
// 读取购物车
const cart = wx.getStorageSync('cart') || [];
3. 性能优化策略
图片懒加载:使用
lazy-load属性实现菜品图片懒加载:
html
分页加载:在
pages/menu/menu.js中实现滚动分页:
javascript
onReachBottom() {
this.page++;
this.loadMoreDishes();
},
loadMoreDishes() {
wx.request({
url: 'https://api.example.com/dishes',
data: { page: this.page, size: 10 },
success: (res) => {
this.setData({ dishes: [...this.data.dishes, ...res.data] });
}
});
}
骨架屏:使用
skeleton组件提升加载体验:
html
四、数字化生态的延伸:数据驱动的运营决策
系统通过ECharts实现数据可视化,在pages/admin/stats.vue中展示销售额趋势:
vue
<script>
import * as echarts from 'echarts';
export default {
data() {
return {
ec: {
onInit: (canvas) => {
const chart = echarts.init(canvas);
chart.setOption({
xAxis: { data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
yAxis: { type: 'value' },
series: [{ type: 'line', data: [120, 200, 150, 80, 70, 110, 130] }]
});
return chart;
}
}
};
}
};
script>
通过用户画像分析实现精准营销,在services/userService.js中计算用户消费频次:
javascript
function calculateUserFrequency(userId) {
return axios.get(`/api/orders?userId=${userId}`)
.then(res => res.data.length);
}
总结与展望
本套源码通过前后端分离架构、跨端开发框架、微服务治理实现了小程序与APP双端融合的餐饮数字化生态。从扫码点餐、购物车管理到订单配送,每个环节都通过源码实现了高效的数据流动与业务闭环。通过这套源码,餐饮企业可快速搭建符合自身需求的点餐外卖系统,实现从线下到线上的数字化跃迁,最终构建起以用户为中心、数据驱动的餐饮新生态。
