news 2026/9/24 22:28:09

Vue+Node.js+微信小程序校园跑腿点餐系统全栈开发实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue+Node.js+微信小程序校园跑腿点餐系统全栈开发实战

几周前帮朋友捣鼓了一个校园跑腿点餐的小项目,前后端加小程序端折腾了大概三天。最近后台数据涨得不错,顺手把整套实现思路整理出来。这篇文章不会讲太多花哨的架构设计,全是实际能跑通的代码路径和踩坑记录,希望对正在做类似毕业设计或者个人项目的朋友有帮助。

这个项目说白了就是三件事:小程序端给用户点餐,Vue后台给商家管菜单和订单,Node.js做中间的数据中转和业务处理。说真的,这套组合在目前的个人项目和中小型公司里太常见了,因为每一环都有大量成熟方案可以抄,组合起来又没有什么明显的硬伤。

如果你正准备做网上订餐类的系统,或者手头有个小程序项目不知道怎么管理后台,这篇文章可以当作一份完整的地图来参考。就算你不是做订餐的,里面关于Vue+Node.js+小程序的配合方式、鉴权思路、订单状态机设计,也能直接搬到其他业务场景。

1. 项目整体设计与技术选型思路

1.1 为什么是 Vue + Node.js + 小程序 这套组合

先说选型。我之前也纠结过要不要用Java后端加Vue全家桶,后来发现Node.js的优势在这个项目里特别明显:前后端都是JavaScript,数据格式自然就是JSON,完全不需要做任何序列化转换。这对于快速开发太关键了,尤其是订单、菜品这类嵌套结构比较多的数据,用JavaScript对象直接传递,省掉了一大堆DTO转换的样板代码。

Vue这边我选的是Vue 2 + Element UI,没上Vue 3,原因很简单:Element UI对Vue 2的支持最成熟,网上案例最多,遇到问题一搜就有答案。对个人项目来说,能快速解决问题比用什么新特性更重要。当然如果你打算长期维护,Vue 3 + Element Plus也不差,API更现代,类型推导更好,但开发效率上提升不大。

小程序端就是微信原生小程序,没有用uni-app或者Taro。原因是这个项目的页面结构比较简单,就一个点餐首页、商品详情、购物车、订单列这几个页面,原生的写法完全够用。而且原生小程序调试起来最方便,而且踩坑资料最多,没必要引入一层跨端框架增加心智负担。

1.2 系统整体架构与模块划分

整个系统分成三部分,对应三个独立的项目工程:

  • 小程序端:用户下单入口,负责菜单浏览、商品加购、下单支付、订单查询
  • Vue管理后台:商家运营入口,负责菜品管理、订单处理、配送状态更新
  • Node.js后端:提供RESTful API,负责用户身份认证、订单业务逻辑、数据持久化

这三者之间的数据流向是:小程序调用后端接口,后端操作数据库,Vue后台也调用后端接口。后端是整个系统的核心枢纽,所有业务逻辑都收敛在这里。

数据库我选了MySQL,没有上MongoDB。客观讲,订单这类事务性强的数据确实需要关系型数据库来保证一致性。虽然MongoDB在开发时不用建表很爽,但后面做订单统计、按时间查询这些需求时,SQL的灵活性和性能都不错。项目早期用MySQL,长期维护也放心。

1.3 开发环境准备的三个关键坑

环境配置这一节我放在最前面讲,因为估计有很多人卡在这一步。这几个问题我在配环境时几乎全踩了一遍,而且群里也有不少朋友反复问。

第一个坑:Node.js版本选择。我刚开始装的是最新的Node.js 20,结果发现有些老项目跑不起来。后来看了下npm官方支持说明,果断换成了Node.js 16 LTS版本。对于个人项目,尽量选LTS版本,不要追新,因为很多依赖包还没有适配最新版,折腾半天都是依赖兼容问题。

第二个坑:npm权限问题。Windows系统上经常遇到“npm无法加载文件npm.ps1,因为在此系统上禁止运行脚本”的报错,这其实是PowerShell执行策略的限制。解决方案是:以管理员身份打开PowerShell,运行Set-ExecutionPolicy RemoteSigned命令,然后选择“Y”确认。这个问题在Windows下太典型了,几乎每个用npm的人都会遇到。

第三个坑:微信小程序开发者工具的AppID。注册小程序需要企业资质或个人资质,如果没有AppID,可以使用测试号。用测试号有很多限制,比如不能使用微信支付,所以如果是要做完整系统,建议提前申请好AppID。我在开发初期为了省事儿用的测试号,结果后面做支付联调时又得重新配置,白白浪费了一些时间。

1.4 Vue后台管理端的脚手架选择

Vue后台我用的vue-element-admin这个开源模板,准确说是基于它的精简版。这个框架网上一抓一大把,用来做后台管理非常顺手,已经集成了登录、权限验证、动态路由、面包屑、标签页这些常用功能,比自己从零开始搭要快太多了。

不过要注意的是,vue-element-admin本身集成的功能很多,直接拿来用会有很多用不上的模块,我当时就花了一晚上做减法,把不相关的页面删掉,只保留登录、Dashboard、商品管理、订单管理这几个核心模块。做减法这步一定不能省,否则后台页面会非常臃肿,自己后面维护也会很累。

2. 数据库设计:订单系统的地基

2.1 核心数据表结构详解

订餐系统的数据库设计是整个项目最需要动脑子的部分。我先梳理一下实体关系:用户要能下单,商家要能管理菜品,每个菜品要归属于某个商家,订单要有菜品明细,订单还要有配送地址和状态变化记录。

我设计了以下这几张核心表:

users(用户表):

  • id:主键,自增
  • openid:微信openid,唯一索引
  • nickname:用户昵称
  • avatar:头像URL
  • phone:手机号
  • create_time:创建时间

restaurants(商家表):

  • id:主键
  • name:店名
  • address:地址
  • logo:店铺logo
  • status:营业状态,0打烊,1营业
  • notice:公告信息

dishes(菜品表):

  • id:主键
  • restaurant_id:所属商家ID,外键
  • name:菜名
  • price:价格,用Decimal(10,2)
  • image:图片URL
  • category:分类,如热菜、凉菜、饮品
  • status:上架状态
  • description:描述
  • stock:库存

orders(订单表):

  • id:主键
  • order_no:订单编号,唯一
  • user_id:用户ID
  • restaurant_id:商家ID
  • total_amount:订单总金额
  • address:收货地址
  • contact_name:联系人
  • contact_phone:联系电话
  • status:订单状态,0待支付,1已支付,2制作中,3配送中,4已完成,5已取消
  • remark:备注信息
  • create_time:下单时间

order_items(订单明细表):

  • id:主键
  • order_id:订单ID
  • dish_id:菜品ID
  • dish_name:菜品名称(冗余存储,防止菜品信息变化后无法追溯)
  • price:购买时的单价
  • quantity:购买数量
  • subtotal:小计金额

delivery_records(配送记录表):

  • id:主键
  • order_id:订单ID
  • delivery_user_id:配送员ID
  • status:配送状态
  • update_time:更新时间
  • location:位置信息

2.2 为什么订单明细要冗余字段

这里有一个值得展开说说的地方,就是订单明细表里为什么还要存dish_name和price快照。刚开始做的时候,我天真地以为直接关联菜品表查名字和价格就行,结果后来发现了一个问题:如果商家改了菜品的价格或者菜品名称,历史订单就会显示出错误的数据。

举个例子,用户周三下单时红烧肉是38元,周四商家涨到45元。等用户周五查看订单记录,如果实时关联菜品表,看到的就是45元,而实际他支付的是38元。这个坑很隐蔽,如果不处理,后面查账会对不上。解决方案就是在下单时把菜品的名称、单价复制一份存入订单明细表,用空间换正确性。

用户下单后商家菜品如何变化,历史订单都不会受影响。这也是正规电商系统的通用做法,不是我这个项目独有的。

2.3 订单状态机的流转设计

订单状态是我这个系统里逻辑最复杂的部分,一定要在设计阶段就规划好。我在项目里定义了一组状态流转规则,各端严格按照这个规则来更新数据:

待支付 → 已支付 → 制作中 → 配送中 → 已完成

每个状态之间的跳转都必须通过后端接口完成,前端不管直接写死状态值。例如用户点击支付,前端只调用后端接口,由后端判断当前状态是否为“待支付”,如果是才允许更新为“已支付”,否则直接返回错误。

这里我还有一个加料的设计:状态变更记录表。每次订单状态变化就插入一条记录,记录订单编号、旧状态、新状态、操作人和操作时间。这个表看起来简单,但后面出问题追责或者用户投诉时,就是最好的排查依据。

3. Node.js后端接口与业务逻辑实现

3.1 项目初始化和核心依赖

后端我用的Express框架,没有用NestJS这种重型框架。原因是项目规模不大,Express的中间件机制足够灵活,社区教程也多。初始化一个Node.js项目很简单,几步就搞定:

mkdir delivery-backend cd delivery-backend npm init -y npm install express mysql2 sequelize jsonwebtoken bcryptjs cors npm install nodemon --save-dev

这些依赖各自的用途:

  • express:web框架,路由和中间件核心
  • mysql2:连接MySQL数据库的驱动,支持Promise
  • sequelize:ORM框架,避免手写SQL
  • jsonwebtoken:生成和验证JWT登录令牌
  • bcryptjs:密码加密(管理端用户要用)
  • cors:解决跨域问题
  • nodemon:开发时热重载,改完代码不用手动重启

3.2 数据库连接与模型定义

我用Sequelize作为ORM,它最大的好处是不用写原生SQL。定义模型的代码看起来是这样的:

const { DataTypes } = require('sequelize'); const sequelize = new Sequelize('delivery_db', 'root', 'password', { host: 'localhost', dialect: 'mysql', timezone: '+08:00' }); const Dish = sequelize.define('Dish', { name: { type: DataTypes.STRING(100), allowNull: false }, price: { type: DataTypes.DECIMAL(10, 2), allowNull: false }, category: { type: DataTypes.STRING(50) }, image: { type: DataTypes.STRING(255) }, status: { type: DataTypes.INTEGER, defaultValue: 1 }, stock: { type: DataTypes.INTEGER, defaultValue: 0 } });

有几点值得注意。第一,时间戳字段要设置timezone: '+08:00',否则Sequelize默认使用UTC时区,存入数据库的时间会比北京时间少8个小时,后面查订单时间会对不上。第二,价格字段一定要用DECIMAL,不能用FLOAT,浮点数计算金额时会有精度丢失的问题,这属于常识级别的问题了。

3.3 JWT用户鉴权流程

小程序的用户登录流程和普通网站不一样,没有密码这一说。流程是这样的:小程序调用wx.login获取一个code,然后把code发给后端,后端拿着这个code去微信接口换取openid,再用openid作为唯一标识去查找或创建用户,最后返回一个JWT令牌给小程序。

router.post('/login', async (req, res) => { const { code, nickname, avatar } = req.body; // 用code换取openid const appid = '你的appid'; const secret = '你的secret'; const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`; const result = await axios.get(url); const { openid } = result.data; // 查找或创建用户 let user = await User.findOne({ where: { openid } }); if (!user) { user = await User.create({ openid, nickname, avatar }); } // 生成JWT令牌 const token = jwt.sign({ id: user.id }, 'your-secret-key', { expiresIn: '7d' }); res.json({ token, user }); });

JWT令牌有效期我设置的7天,这样用户不用每次都重新登录,体验比较好。密钥一定要放到环境变量里,不要写死在代码中,虽然个人项目影响不大,但养成好习惯总没错。

3.4 下单与支付的核心接口实现

下单接口是整个系统的核心,里面涉及事务处理。什么场景下必须用事务?就是同时写多张表的场景。下单这个操作要写订单表、订单明细表,还要更新菜品库存,这三个操作必须绑在一起,要么全部成功,要么全部失败,否则就会出现订单建了但库存没扣,或者库存扣了但订单没成的脏数据。

const transaction = await sequelize.transaction(); try { // 创建订单 const order = await Order.create({ order_no: generateOrderNo(), user_id: userId, restaurant_id: restaurantId, total_amount: totalAmount, status: 0 }, { transaction }); // 批量创建订单明细 const items = cartItems.map(item => ({ order_id: order.id, dish_id: item.dishId, dish_name: item.dishName, price: item.price, quantity: item.quantity })); await OrderItem.bulkCreate(items, { transaction }); // 扣减库存 for (const item of cartItems) { await Dish.decrement('stock', { by: item.quantity, where: { id: item.dishId } }); } await transaction.commit(); } catch (error) { await transaction.rollback(); res.status(500).json({ message: '下单失败' }); }

订单号生成这块也有讲究。我用的是时间戳加随机数的方式:yyyyMMddHHmmss + 4位随机数,基本保证唯一。当然如果要更严谨,可以加一个用户ID的片段,进一步降低重复概率。千万不要用自增ID当订单号,一是容易暴露订单量,二是多表关联时凭订单号看不出时间信息。

真实支付我做了个模拟接口,直接让用户选择模拟支付成功,然后后端把订单状态从“待支付”改为“已支付”。如果要接入微信支付,需要企业资质、商户号,开通流程比较长,个人项目建议先用模拟支付把整个流程跑通,后面有条件再替换。

3.5 配送状态更新的实现

配送功能是订餐系统区别于普通商城的一个重点。我在设计与逻辑上比较倾向于简化角色,把配送员和商家统一成后台管理端的角色,即商家自己处理接单和配送。这样在小规模试点时可以省掉一个配送员的角色,等业务大了再把配送员拆出来。

配送状态更新接口的业务规则是这样的:只有订单当前状态为“已支付”时,商家才能点“开始制作”,把状态改成“制作中”;只有“制作中”才能改成“配送中”;只有“配送中”才能改成“已完成”。这其实就是前面说的状态机,每个接口进来先校验当前状态,不满足条件直接返回错误信息。

4. 小程序端点餐流程与购物车实现

4.1 项目目录结构与页面规划

小程序端我按照业务模块划分目录,每个页面独立文件夹,结构清晰:

miniprogram/ ├── pages/ │ ├── index/ // 首页:店铺列表、菜品列表 │ ├── detail/ // 菜品详情页 │ ├── cart/ // 购物车页面 │ ├── order/ // 订单确认页 │ ├── order-list/ // 订单列表 │ ├── order-detail/ // 订单详情页 │ └── user/ // 个人中心 ├── utils/ │ ├── request.js // 封装wx.request请求 │ ├── auth.js // 登录鉴权相关 │ └── cart.js // 购物车本地缓存管理 ├── app.js // 小程序入口文件 ├── app.json // 全局配置 └── app.wxss // 全局样式

首页的逻辑是:进入后先请求后端接口获取所有营业中的商家,点击某个商家后,再获取该商家的菜品列表,按照分类显示。我用的是九宫格式布局,菜品以卡片形式展示,点击进入详情页。

4.2 购物车状态管理的两种方案

购物车是小程序端的核心状态,我用了本地缓存+全局数据的组合方案。具体来说:

  • wx.setStorageSync把购物车数据持久化到本地,这样即使小程序被杀掉重新打开,购物车数据也不会丢
  • app.globalData.cart保存内存中的购物车数据,这样页面切换时不需要反复读缓存,性能更好
  • 每次增删商品时,同时更新内存数据和本地缓存,两个地方保持同步

购物车的数据结构是这样的:

{ "restaurantId": 1, "items": [ { "dishId": 10, "name": "回锅肉", "price": 28, "quantity": 2, "image": "..." } ] }

这里有一个很重要的规则:购物车只能同时包含一个商家的菜品。用户如果加了A商家的菜,再去加B商家的菜,我会弹窗提示他“购物车已有其他商家的商品,是否清空并重新添加”。因为一次配送只能对应一个商家,如果跨商家下单,配送费用和配送逻辑都会变得非常麻烦。这个限制从用户视角看也完全合理。

4.3 小程序端请求封装与登录流程串联

小程序的wx.request每次都要写一大堆参数,所以我封装了一个request工具函数,把公共逻辑抽出来:

const request = (url, method = 'GET', data = {}) => { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method, data, header: { 'Content-Type': 'application/json', 'Authorization': getToken() // 从storage中取token }, success: (res) => { if (res.statusCode === 401) { // token过期,重新登录 loginAndRequest(url, method, data).then(resolve).catch(reject); } else { resolve(res.data); } }, fail: (err) => reject(err) }); }); };

登录时序是小程序端最容易疏忽的地方。正确的流程是:小程序启动时先检查storage里有没有token,有就直接用;没有的话调用wx.login获取code,发给后端换token,然后存起来。这个流程要在app.js的onLaunch里执行,并且要确保在后续接口调用前登录完成。

我用了Promise把登录包装成异步操作,所有请求先等待登录状态就绪再发出。具体实现思想是:假如用户第一次打开就快速点击点餐,此时登录还没完成,我们不能直接发未带token的请求。我的方案是维护一个loginPromise变量,初始为null,第一次调用login时赋值Promise,后续的调用都复用同一个Promise,保证登录请求只发一次。

4.4 订单确认页与支付流程的细节

订单确认页需要展示以下内容:

  • 收货人信息:姓名、电话、详细地址
  • 商品清单:从购物车数据读取,这里数据不重新请求后端,直接用本地缓存
  • 配送费:按距离或固定金额计算,我这边做的是满30元免配送费,不满收5元
  • 订单备注:用户填写的备注信息,一起传到后端

用户点击“提交订单”后,先调用后端创建订单接口,拿到订单号和订单ID,然后进入支付环节。我做的模拟支付是弹出一个确认框,点击确认后调用支付接口模拟成功,然后跳转到订单列表,并把购物车清空。

这个环节容易出的问题是重复点击提交按钮。网络慢的情况下用户连续点两次,可能创建两个一模一样的订单。我处理的方式是按钮点击后立即置disabled状态并显示“提交中...”,等接口返回后再恢复。这属于前端交互的基本功,但很多人会忽略。

4.5 订单状态轮询实现

用户下单后,订单状态的更新是由商家在后台操作的。小程序端如何及时感知状态变化?我用了简单的轮询机制:在订单详情页每10秒请求一次订单详情接口,如果状态发生变化就刷新页面数据。

轮询实现很简单,在onShow里启动定时器:

onShow() { this.startPolling(); }, onHide() { this.stopPolling(); }, methods: { startPolling() { this.pollingTimer = setInterval(() => { this.fetchOrderDetail(); }, 10000); }, stopPolling() { if (this.pollingTimer) { clearInterval(this.pollingTimer); } } }

定时器的启动和停止一定要记得绑定页面的onShow和onHide生命周期。有一次我就是只开了定时器,忘记在onHide里关掉,页面退回列表后定时器还在跑,一直在疯狂请求接口,被同事笑称为“内存泄漏教科书案例”。

如果追求更好的实时性,可以用微信小程序的WebSocket能力,服务端主动推送状态变化。但WebSocket会增加后端复杂度,对个人项目来说没必要,10秒轮询完全够用。

5. Vue后台管理端的实现

5.1 路由和权限控制

Vue后台我基于vue-element-admin模板改造,路由表分为两部分:一部分是固定的路由(登录页、404页),另一部分是动态路由(需要登录后根据角色权限动态添加)。我这边给后台做了一个简单的管理员表,没有做路由级权限控制,默认只有一种“管理员”角色,登录后所有管理页面都可以访问。

如果你需要更细致的权限控制,可以在前端定义好页面和角色的映射关系,登录后根据后台返回的角色信息过滤出可见路由,再用router.addRoutes动态添加。这个设计方案是vue-element-admin的经典思路,网上的资料很多。

5.2 菜品管理页面的表格和表单校验

菜品管理页是典型的表格+弹窗表单组合:页面主体是一个表格,展示所有菜品信息,每行有“编辑”和“下架”按钮;点击“新增”或“编辑”按钮时,弹出一个弹窗表单,填写菜品信息。

表单校验我用了Element UI自带的校验规则,比如:

  • 菜名必填,长度2~20个字符
  • 价格必填,且必须是大于0的数字,保留两位小数
  • 库存必填,且必须是大于等于0的整数
  • 分类必须选择

价格和库存的校验要用自定义函数实现,Element UI自带的type: 'number'在某些版本下存在bug,会无法正确校验小数。

图片上传用Element UI的el-upload组件,通过action属性指定后端的上传接口。后端用multer中间件处理文件上传,把图片保存到本地目录,然后返回图片访问URL。要注意的是,Nginx部署时要配置好静态文件的访问路径,不然图片是上传成功了,但前端访问不了。

5.3 订单处理的操作流转设计

后台订单管理页面的核心功能是处理订单状态流转。我设计的是每行订单都显示一个操作按钮,根据当前状态显示可执行的操作:

  • 待支付状态:显示“取消订单”
  • 已支付状态:显示“开始制作”和“取消订单”
  • 制作中状态:显示“开始配送”
  • 配送中状态:显示“确认完成”

每次操作前先调用后端接口更新状态,成功后再刷新表格数据。在操作按钮的地方加了二次确认的MessageBox,避免误操作导致订单状态错误。

对于商家来说,订单列表要支持按状态筛选,因为订单量大了以后如果所有订单都混在一起,处理效率会非常低。我用的是el-tabs的方式把不同状态的订单分开展示,待处理的放前面,已完成的折叠起来。

5.4 数据统计与可视化

这个项目我还加了一个简单的数据统计页面,用ECharts展示几个关键指标:

  • 近7天订单量折线图
  • 菜品销量排行TOP10柱状图
  • 订单状态分布饼状图

统计功能的后端接口,用SQL的GROUP BY和DATE_FORMAT函数就能实现。比如近7天订单量,只需要分组聚合:

SELECT DATE(create_time) as date, COUNT(*) as count FROM orders WHERE create_time >= DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY DATE(create_time) ORDER BY date ASC;

这个统计页面对商家的参考价值挺高的,可以看出哪些菜卖得最好,哪些时间段订单最多,方便调整备货和营业时间。

6. 前后端联调与部署上线的常见问题

6.1 跨域问题的一劳永逸解决方案

前后端分离开发时,Vue后台跑在http://localhost:9527,后端跑在http://localhost:3000,两者端口不同,就会遇到跨域问题。

我的方案是后端使用cors中间件全局开启跨域支持:

const cors = require('cors'); app.use(cors());

这样配置后,开发环境和生产环境都不需要额外处理跨域。当然这只适用于没有敏感凭证的API场景。如果涉及Cookie会话,就需要配置CORS的credentials选项,并指定白名单域名,要复杂一些。

小程序的请求不存在浏览器跨域问题,因为小程序是原生客户端请求,不受浏览器同源策略限制。但在微信开发者工具里,需要勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”选项,否则会报域名校验错误。

6.2 小程序预览时接口地址怎么配

开发小程序时会遇到一个经典问题:API请求地址怎么配。开发阶段,小程序跑在微信开发者工具里,手机预览时,请求要发到本机电脑的Node.js服务。这时候要注意:

  • 如果使用微信开发者工具的模拟器,可以直接用http://localhost:3000访问本机服务
  • 如果使用真机预览,手机不能直接访问电脑的localhost,需要换成电脑在局域网中的IP地址
  • 同时要保证手机和电脑连接的是同一个WiFi

更麻烦的是,微信小程序正式版本要求所有请求地址必须是HTTPS域名,且域名要备案并在小程序后台配置为合法域名。个人开发阶段用开发者工具的“不校验域名”选项跳过校验没关系,但要上线的话必须解决域名和HTTPS证书的问题。

我建议的路径是:开发阶段用局域网IP,测试阶段用测试域名加自签名证书,上线前再切换正式HTTPS域名。

6.3 两个部署场景的方案参考

部署后端我提供了两种方案:

方案一:本机直接跑。适合个人开发和内网测试。用pm2管理Node.js进程,确保进程崩溃后自动重启,开机自启动可以用pm2-windows-service或pm2 startup命令实现。数据库直接用本机安装的MySQL。

方案二:云服务器部署。我是用一台Linux服务器,nginx做反向代理,把/api路径转发到Node.js的3000端口。MySQL装在同一台服务器上,方便管理。部署脚本可以写成这样的格式:

cd /var/www/delivery-backend git pull origin main npm install --production pm2 restart ecosystem.config.js

前端Vue后台打包后,把dist目录下的静态文件放到nginx的web目录,通过nginx直接服务。小程序端不用部署,微信审核通过后会自动发布到微信服务器。

6.4 开发过程中最容易踩的几个坑

这一节我专门整理一下开发过程中反复踩到的坑,按出现频率排序:

第一,npm相关的问题。除了前面说到的PowerShell脚本执行策略问题外,还有npm install时经常遇到权限错误或网络超时。我的经验是先把npm registry切到国内镜像源,安装速度会快很多,也基本不会出现timeout问题:

npm config set registry https://registry.npmmirror.com

第二,微信开发者工具提示“开发版小程序已过期”。这个提示通常是因为上一次的预览码过期了,重新点击开发者工具工具栏的“预览”按钮,生成新的预览码扫码即可。如果在手机上还是打不开,关掉小程序后台进程重新进入。

第三,小程序动态设置顶部标题。如果需要在页面运行过程中修改导航栏标题,不能直接修改app.json里的navigationBarTitleText,而要在页面的onLoad或onShow生命周期里调用wx.setNavigationBarTitle接口,动态修改标题。

第四,前后端字段名不一致。我有一个惨痛教训:后端返回的字段是createTime(驼峰命名),前端表单提交时用了create_time(下划线命名),结果数据一直绑定不上。后来在Sequelize配置里加了underscored: true,自动把模型属性转换为下划线风格,这个问题才算解决。前端联调时一定要先确认好字段命名规范,最好有一个统一的对照文档。

第五,Node.js进程挂了。开发阶段我用nodemon,但上线部署后nodemon就不合适了。至少有两次线上服务挂了,是因为没有用pm2守护进程。后来在pm2的配置文件里配置了监听模式,才能解决。

6.5 接口响应速度优化心得

最后聊一下接口性能优化。订餐系统虽然不算高并发场景,但接口响应快慢直接影响用户体验。

我做的优化主要有几点:

  • 数据库查询一定要加索引。orders表的user_id、restaurant_id、status字段,order_items表的order_id字段,都要建索引。没有索引的时候,订单量过千就明显变慢,加了索引后查询基本在毫秒级。
  • 列表接口用分页。菜品列表和订单列表都实现分页,每页限制20条。后端用Sequelize的limit和offset实现,前端加一个加载更多的按钮。
  • 图片走CDN。菜品图如果自己服务器带宽不够,前端加载会特别慢。我是把图片传到对象存储服务上,然后通过CDN加速访问,效果立竿见影。
  • 首页数据做缓存。菜单列表和商家列表这种变化不频繁的数据,在后端加一个简单的内存缓存,5分钟刷新一次,能显著减少数据库压力。

7. 写在最后的经验总结

项目做完之后,我最大的感受是:快捷交付的真正技术难点,不在于某一个单独技术的深挖,而在于把三个端口的业务逻辑串起来。数据从什么页面开始、走到哪个接口、改变什么状态,这些链条一旦打通,整个系统就活了。

我个人在Node.js后端加了一个日志中间件,每次有接口请求都打印请求路径、参数、响应耗时,调试时候反馈非常快。也强烈建议你加一个类似的东西。还有一个习惯,就是每次接口写完,先用Postman把边界情况测一遍,比如菜品库存为0时下单、订单状态异常时流转,等接口稳定了再对接前端,这样可以少走很多弯路。

这个项目后续要扩展的话,方向也蛮清晰:一是接入真实微信支付,替换掉模拟支付;二是增加配送员角色和订单派单逻辑;三是做一个基于位置的门店推荐,用wx.getLocation获取用户位置后按距离排序。希望这套思路能帮到你。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 22:27:55

OnchainOS:给 AI Agent 一个可信的链上运行环境

大概从去年年底开始,我身边越来越多的技术朋友开始讨论一个话题:AI Agent 到底什么时候能真正"自主干活"而不是只会写周报。大家发现,模型本身已经够聪明了,卡住的往往是工程化——Agent 做着做着状态丢了,多…

作者头像 李华
网站建设 2026/9/24 22:27:32

谷物害虫目标检测:687张YOLO标注数据集训练与避坑实战

简介:谷物害虫目标检测数据集面向粮仓存储环境中的常见害虫自动检测需求,适用于农业AI开发者、智能仓储系统研发人员以及目标检测入门学习者。整包共1376个文件,包含687张实拍害虫图像与687份对应的YOLO格式标注txt,同时附有1个ya…

作者头像 李华
网站建设 2026/9/24 22:27:32

OpenCV实现视频车辆测速:检测、标定与跟踪实战

简介:面向视频车辆测速这一具体任务,一套基于Python与OpenCV的实践资源,适配计算机视觉入门者、智能交通方向学生及有课程设计需求的开发者,覆盖车辆检测、目标跟踪与速度估算的完整流程,可与B站配套演示视频配合学习。…

作者头像 李华
网站建设 2026/9/24 22:27:21

Windows原生部署DeepSeek:Ollama+环境变量+GGUF模型实战指南

1. 项目概述:为什么要在Windows上跑DeepSeek?这不是“能用就行”的事DeepSeek系列模型——尤其是DeepSeek-V2、DeepSeek-Coder、DeepSeek-MoE这些开源大模型——最近半年在开发者圈子里热度飙升。不是因为它们参数量最大,而是因为实测下来&am…

作者头像 李华
网站建设 2026/9/24 22:26:03

中职网络安全赛AB模块解析:Nmap扫描与iptables加固实战

简介:这份资源是2022年全国职业院校技能大赛(中职组)网络安全竞赛试题1套的A、B模块答案解析,面向备战全国职业院校技能大赛的中职选手、指导教师以及需要系统梳理网络安全实操要点的学习者。内容围绕基础设施设置与安全加固、网络…

作者头像 李华
网站建设 2026/9/24 22:25:08

打造AI安全审计技能:SKILL.md设计与落地实践

最近我把团队内部用了两个月的security-audit-skill整理成了独立仓库。起因很直接:团队里几个主力开发都开始用 Claude Code 和 Codex 写业务代码,AI 产出速度确实快,但安全上总是漏风。更尴尬的是,你让模型直接做代码审计&#x…

作者头像 李华