1. 项目概述:私厨上门服务系统的技术实现
这个基于Node.js的全栈项目,本质上是一个连接专业厨师与家庭用户的O2O平台。我选择用uni-app+vue实现跨端小程序,配合Node.js后端服务,主要解决传统餐饮服务中"想吃家常菜却不会做"的痛点。去年帮朋友改造他的私厨工作室时,我们实测发现:通过移动端预约可以提升厨师40%的时间利用率。
整套系统包含三个核心模块:用户端小程序展示层(uni-app+Vue)、业务逻辑层(Node.js+Express)、数据持久层(MongoDB)。这种技术栈组合特别适合快速迭代的本地生活服务类项目——uni-app的跨端特性让一套代码能同时发布到微信、支付宝等平台,而Node.js的非阻塞I/O模型在高并发预约场景下表现优异。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构深度解析
2.1 跨端框架选型对比
最初技术选型时,我们对比了三种方案:
- 纯原生开发(性能最优但维护成本高)
- Taro框架(React语法生态)
- uni-app(Vue语法生态)
最终选择uni-app主要基于三点考量:
- 团队现有Vue技术栈积累,学习曲线平缓
- 完善的插件市场(特别是uView UI组件库)
- 官方提供的云打包服务简化发布流程
实际开发中,这些特性确实带来了显著效率提升。比如通过uni-app的条件编译,可以这样处理多平台差异:
javascript复制// #ifdef MP-WEIXIN
wx.login({...})
// #endif
// #ifdef MP-ALIPAY
my.getAuthCode({...})
// #endif
2.2 后端服务设计要点
Node.js后端采用分层架构,核心目录结构如下:
code复制server/
├── config/ # 环境配置
├── controllers/ # 业务逻辑
├── models/ # 数据模型
├── routes/ # 路由定义
├── middlewares/ # 中间件
└── utils/ # 工具函数
特别要注意的是预约冲突检测的实现。我们在MongoDB中为厨师时间表建立了复合索引:
javascript复制chefScheduleSchema.index({
chefId: 1,
startTime: 1,
endTime: 1
}, { unique: true });
配合这个定时任务检查算法:
javascript复制function checkAvailability(existing, newBooking) {
return existing.every(item =>
newBooking.endTime <= item.startTime ||
newBooking.startTime >= item.endTime
);
}
3. 核心功能实现细节
3.1 实时预约状态同步
使用WebSocket实现订单状态实时推送是关键体验。我们在Node.js端采用ws库:
javascript复制const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
const { userId, type } = JSON.parse(message);
// 加入对应房间
if(type === 'subscribe') {
ws.userId = userId;
}
});
});
// 订单状态变更时广播
function broadcastOrderUpdate(order) {
wss.clients.forEach(client => {
if(client.userId === order.userId) {
client.send(JSON.stringify(order));
}
});
}
前端通过uni-app的socketTask封装:
javascript复制const socketTask = uni.connectSocket({
url: 'wss://yourdomain.com',
success: () => {
socketTask.send({
data: JSON.stringify({
userId: '123',
type: 'subscribe'
})
});
}
});
3.2 支付系统集成
支付流程涉及多个状态管理,我们采用状态机模式:
mermaid复制stateDiagram
[*] --> 待支付
待支付 --> 已支付: 用户完成支付
待支付 --> 已取消: 超时未支付
已支付 --> 已完成: 服务结束
已支付 --> 退款中: 用户申请退款
退款中 --> 已退款: 商家确认
退款中 --> 已支付: 商家拒绝
具体实现时要注意:
- 微信支付需要配置合法的业务域名
- 支付宝公钥需要定期更新
- 务必实现对账接口处理异常情况
4. 性能优化实战记录
4.1 小程序首屏加载优化
通过分包策略将首屏加载从2.1s降至1.3s:
- 主包只保留核心页面(首页、登录页)
- 按功能划分分包:
json复制{
"subPackages": [
{
"root": "packageA",
"pages": ["pages/chef/list", "pages/chef/detail"]
},
{
"root": "packageB",
"pages": ["pages/order/create", "pages/order/list"]
}
]
}
- 图片资源采用CDN加速+WebP格式
- 关键数据预加载:
javascript复制onLoad() {
uni.preloadPage({ url: '/pages/chef/list' });
}
4.2 数据库查询优化
针对高频访问的厨师列表接口,我们做了三级缓存:
- 内存缓存(最近5分钟数据)
javascript复制const cache = new Map();
async function getChefList(region) {
if(cache.has(region)) {
return cache.get(region);
}
// ...数据库查询
cache.set(region, result);
setTimeout(() => cache.delete(region), 300000);
return result;
}
- MongoDB查询优化:
javascript复制Chef.find()
.select('name avatar rating specialty')
.sort('-rating')
.lean()
.limit(20)
- 前端本地缓存(uni.setStorage)
5. 部署与运维实战
5.1 PM2高级配置
生产环境使用PM2集群模式:
bash复制pm2 start app.js -i max --name "chef-service" --time
对应的ecosystem.config.js配置:
javascript复制module.exports = {
apps: [{
name: "chef-service",
script: "app.js",
instances: "max",
exec_mode: "cluster",
max_memory_restart: "500M",
env: {
NODE_ENV: "production"
}
}]
}
5.2 监控与告警方案
我们采用PM2+阿里云CMS的组合方案:
- PM2内置监控:
bash复制pm2 monit
- 关键指标采集:
javascript复制// 接口响应时间埋点
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
recordMetric('response_time', Date.now() - start);
});
next();
});
- 异常报警规则配置:
- CPU持续>80%达5分钟
- 内存使用>90%
- 500错误率>1%
6. 典型问题排查实录
6.1 微信登录失败排查
常见问题现象:获取openid时报错"invalid code"
解决方案步骤:
- 检查服务器时间是否同步
bash复制ntpdate ntp.aliyun.com
- 确认code使用规范:
javascript复制// 错误!code不能重复使用
// 正确做法:每次登录生成新code
async function wxLogin(code) {
const { openid } = await getOpenid(code);
const token = generateToken(openid);
return { token };
}
- 检查微信开发者平台配置:
- 正确的appid/appsecret
- 配置了合法的request域名
6.2 数据库连接泄漏
现象:运行一段时间后出现"Too many connections"
解决方法:
- 检查连接池配置:
javascript复制mongoose.connect(uri, {
poolSize: 50, // 连接池大小
socketTimeoutMS: 30000,
connectTimeoutMS: 30000
});
- 添加连接状态监控:
javascript复制mongoose.connection.on('connected', () => {
console.log(`MongoDB connected: ${++connectionCount}`);
});
mongoose.connection.on('disconnected', () => {
console.log(`MongoDB disconnected: ${--connectionCount}`);
});
- 使用连接包装器确保释放:
javascript复制async function withDatabase(fn) {
const conn = await mongoose.createConnection(uri);
try {
return await fn(conn);
} finally {
await conn.close();
}
}
7. 项目扩展方向
7.1 智能推荐升级
现有系统可以加入:
- 用户口味分析(基于历史订单)
python复制# 简单的协同过滤算法示例
def recommend_chefs(user_preferences, all_chefs):
scores = []
for chef in all_chefs:
score = sum(
user_preferences.get(tag, 0) * chef.tags.get(tag, 0)
for tag in user_preferences
)
scores.append((chef.id, score))
return sorted(scores, key=lambda x: -x[1])[:5]
- 时令菜品推荐
javascript复制// 根据季节推荐菜品
function getSeasonalDishes() {
const month = new Date().getMonth() + 1;
if(month >=3 && month <=5) {
return ['春笋', '香椿'];
}
// 其他季节...
}
7.2 物联网设备集成
未来可扩展:
- 智能厨电状态监控
c复制// 模拟STM32数据采集
void read_temperature() {
float temp = sensor.read();
if(temp > 100.0) {
trigger_alarm();
}
}
- 厨房安全监测
python复制# OpenCV火焰检测简化示例
def detect_fire(frame):
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_red = np.array([0, 100, 100])
upper_red = np.array([10, 255, 255])
mask = cv2.inRange(hsv, lower_red, upper_red)
return np.sum(mask) > FIRE_THRESHOLD
8. 开发心得与建议
经过三个版本的迭代,总结出几条关键经验:
- 状态管理要趁早
- 初期使用vuex管理全局状态
- 复杂表单使用v-model+computed
- 跨页面通信用eventBus要谨慎
- 错误处理黄金法则
javascript复制// 不好的做法
try {
await someAsyncCall();
} catch(e) {
console.log(e);
}
// 推荐做法
try {
await someAsyncCall();
} catch(e) {
console.error('[API] 调用失败:', e);
Sentry.captureException(e);
showToast('操作失败,请重试');
}
- 性能优化 checklist
- 数据库查询是否用了索引?
- 图片是否压缩+CDN?
- 是否合理使用缓存?
- 是否避免了重复渲染?
- 团队协作建议
- 使用husky统一git hooks
- 代码提交遵循Conventional Commits
- API文档用Swagger自动生成
- 搭建内部组件库文档(storybook)
