ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从零搭建微信小程序外卖商城:前后端分离架构与核心功能实现

从零搭建微信小程序外卖商城:前后端分离架构与核心功能实现 最近在做一个校园外卖项目发现很多同学对如何从零搭建一个功能完整的微信小程序外卖商城平台很感兴趣但网上的资料要么太零散要么只讲前端或后端很难形成一个完整的闭环。本文将手把手带你搭建一个基于微信小程序的外卖商城平台从前端页面到后端接口再到数据库设计提供完整的源码和详细的部署教程。无论你是刚入门的小白还是有经验的开发者想快速复用一套成熟方案都能从本文中找到清晰的路径。1. 项目背景与核心概念1.1 什么是微信小程序外卖商城平台简单来说这是一个运行在微信生态内的“迷你App”用户无需下载安装扫码或搜索即可使用。外卖商城平台则是在此基础上集成了商品浏览、购物车、在线下单、支付、订单管理、配送跟踪等核心电商功能。它解决了传统H5页面体验不佳、App开发成本高、用户获取难等问题尤其适合餐饮、零售、校园服务等高频、本地化的消费场景。1.2 为什么选择微信小程序用户基数庞大依托微信触达用户几乎没有门槛。开发成本相对较低一套代码可适配iOS和Android且微信提供了丰富的原生API如支付、定位、分享。体验接近原生小程序框架提供了流畅的动画和组件用户体验良好。易于推广支持扫码、分享、公众号关联等多种引流方式。1.3 本项目的技术栈与功能概览本项目将采用前后端分离的架构这也是目前主流的开发模式。前端使用微信小程序原生框架WXML、WXSS、JavaScript进行开发。结构清晰学习曲线平缓能充分发挥小程序性能。后端采用 Node.js Koa2 框架。Node.js 异步非阻塞的特性适合高并发的I/O操作如数据库查询、接口调用Koa2 中间件机制让开发更优雅。数据库选用 MySQL。关系型数据库在事务处理如订单、库存和数据一致性方面有优势社区成熟资料丰富。核心功能模块用户端微信授权登录、首页商品展示、商品分类与搜索、购物车管理、在线支付集成微信支付、订单创建与状态追踪、地址管理、个人中心。管理端通常为独立的H5或PC端本文会简述接口设计商品上下架、订单处理、数据统计等。2. 环境准备与版本说明在开始编码之前请确保你的开发环境已就绪。以下是本文示例所使用的环境版本如果你的环境不同请注意调整依赖版本。2.1 前端开发环境操作系统Windows 10 / macOS / Linux 均可。微信开发者工具这是开发、调试、预览小程序的官方IDE。请前往微信公众平台下载并安装最新稳定版。Node.js用于后端开发。建议安装 LTS长期支持版本如v18.x或v20.x。可在官网下载安装包。代码编辑器推荐 Visual Studio Code并安装小程序开发相关插件如minapp。2.2 后端开发环境Node.js同上确保已安装。MySQL版本5.7或8.0。可以使用安装包也可以使用 Docker 快速部署。数据库管理工具推荐 Navicat、DBeaver 或 MySQL Workbench用于直观地操作数据库。Postman 或 Apifox用于测试后端 API 接口。2.3 项目初始化与结构在开始前我们先创建项目的整体目录结构。这有助于代码的组织和维护。weixin-food-delivery/ ├── miniprogram/ # 微信小程序前端代码 │ ├── pages/ # 小程序页面 │ ├── components/ # 自定义组件 │ ├── utils/ # 工具函数 │ ├── app.js # 小程序入口文件 │ ├── app.json # 小程序全局配置 │ ├── app.wxss # 全局样式 │ └── project.config.json # 项目配置 ├── server/ # Node.js 后端代码 │ ├── config/ # 配置文件数据库、微信支付等 │ ├── controller/ # 控制器处理业务逻辑 │ ├── model/ # 数据模型定义表结构、操作数据库 │ ├── router/ # 路由定义API接口 │ ├── middleware/ # 中间件如身份验证、日志 │ ├── utils/ # 工具函数 │ ├── app.js # Koa应用入口文件 │ └── package.json # 项目依赖 └── database/ # 数据库SQL文件 └── init.sql # 初始化数据库表结构的SQL脚本你可以先在本地创建一个名为weixin-food-delivery的文件夹然后按照上述结构创建子文件夹。3. 数据库设计与核心表结构数据库是项目的基石良好的设计至关重要。我们主要设计以下几张核心表。3.1 用户表 (user)存储用户信息主要通过微信授权获取。-- 文件database/init.sql CREATE TABLE user ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 用户ID, openid varchar(100) NOT NULL COMMENT 微信用户唯一标识, nickname varchar(100) DEFAULT NULL COMMENT 微信昵称, avatar_url varchar(500) DEFAULT NULL COMMENT 微信头像, phone varchar(20) DEFAULT NULL COMMENT 手机号, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) COMMENT 确保openid唯一 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;为什么需要openidopenid是用户在同一个微信小程序内的唯一标识是后端识别用户身份的关键所有需要登录态的操作如下单、查看订单都依赖它。3.2 商品表 (product)存储商品信息。CREATE TABLE product ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 商品ID, name varchar(200) NOT NULL COMMENT 商品名称, description text COMMENT 商品描述, price decimal(10,2) NOT NULL COMMENT 商品价格, original_price decimal(10,2) DEFAULT NULL COMMENT 原价用于显示折扣, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存, image_url varchar(500) DEFAULT NULL COMMENT 主图URL, category_id int(11) DEFAULT NULL COMMENT 分类ID, status tinyint(1) DEFAULT 1 COMMENT 状态1-上架0-下架, sales int(11) DEFAULT 0 COMMENT 销量, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表;3.3 订单表 (order) 与订单商品表 (order_item)这是核心的业务表。通常将订单主信息和订单商品明细分开存储这符合数据库设计范式也便于查询。-- 订单主表 CREATE TABLE order ( id varchar(32) NOT NULL COMMENT 订单号建议用时间戳随机数生成非自增, user_id int(11) NOT NULL COMMENT 用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, pay_amount decimal(10,2) NOT NULL COMMENT 实际支付金额, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 订单状态0-待支付1-已支付/待接单2-制作中3-配送中4-已完成5-已取消, address text NOT NULL COMMENT 收货地址JSON字符串包含联系人、电话、详细地址, remark varchar(500) DEFAULT NULL COMMENT 用户备注, pay_time datetime DEFAULT NULL COMMENT 支付时间, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_status (status), KEY idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; -- 订单商品明细表 CREATE TABLE order_item ( id int(11) NOT NULL AUTO_INCREMENT, order_id varchar(32) NOT NULL COMMENT 订单号, product_id int(11) NOT NULL COMMENT 商品ID, product_name varchar(200) NOT NULL COMMENT 商品名称下单时的快照, product_price decimal(10,2) NOT NULL COMMENT 商品单价下单时的快照, quantity int(11) NOT NULL COMMENT 购买数量, total_price decimal(10,2) NOT NULL COMMENT 小计金额, PRIMARY KEY (id), KEY idx_order (order_id), CONSTRAINT fk_order_item_order FOREIGN KEY (order_id) REFERENCES order (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单商品明细表;为什么订单号不用自增ID自增ID容易暴露业务量且长度固定。使用自定义规则如年月日时分秒随机数生成的订单号更灵活也更具业务含义。为什么商品信息要快照存储商品价格、名称可能会变。将下单瞬间的信息存入订单明细可以保证订单历史数据的准确性不受后续商品信息变更的影响。4. 后端服务搭建与核心接口实现接下来我们搭建 Node.js Koa2 的后端服务。4.1 初始化后端项目进入server目录初始化项目并安装核心依赖。cd server npm init -y安装依赖npm install koa koa-router koa-bodyparser koa-static mysql2 dotenv npm install -D nodemonkoa: Web框架核心。koa-router: 路由中间件。koa-bodyparser: 解析请求体如JSON。koa-static: 静态文件服务可用于上传的图片。mysql2: MySQL数据库驱动性能较好。dotenv: 从.env文件加载环境变量。nodemon: 开发工具监听文件变化自动重启。4.2 基础配置与数据库连接创建配置文件.env记得添加到.gitignore:# 文件server/.env DB_HOSTlocalhost DB_PORT3306 DB_USERroot DB_PASSWORDyour_password DB_DATABASEfood_delivery WX_APPIDyour_weixin_appid WX_SECRETyour_weixin_secret创建数据库连接工具// 文件server/utils/db.js const mysql require(mysql2/promise); // 使用Promise版本 require(dotenv).config(); const pool mysql.createPool({ host: process.env.DB_HOST, port: process.env.DB_PORT, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_DATABASE, waitForConnections: true, connectionLimit: 10, // 连接池大小 queueLimit: 0 }); module.exports pool;4.3 实现用户登录接口微信小程序登录流程是前端调用wx.login()获取code传给后端后端用code加上appid和secret去微信服务器换取openid和session_key。// 文件server/controller/userController.js const pool require(../utils/db); const axios require(axios); // 需要安装: npm install axios class UserController { // 微信登录 static async login(ctx) { const { code } ctx.request.body; if (!code) { ctx.status 400; ctx.body { code: 400, message: 缺少code参数 }; return; } const appid process.env.WX_APPID; const secret process.env.WX_SECRET; // 微信接口地址 const url https://api.weixin.qq.com/sns/jscode2session?appid${appid}secret${secret}js_code${code}grant_typeauthorization_code; try { // 1. 向微信服务器请求 openid const response await axios.get(url); const { openid, session_key, errcode, errmsg } response.data; if (errcode) { ctx.status 500; ctx.body { code: 500, message: 微信接口错误: ${errmsg} }; return; } // 2. 根据openid查找或创建用户 let [users] await pool.query(SELECT * FROM user WHERE openid ?, [openid]); let user users[0]; if (!user) { // 新用户插入数据库 const [result] await pool.query( INSERT INTO user (openid, create_time) VALUES (?, NOW()), [openid] ); user { id: result.insertId, openid }; } // 3. 生成自定义登录态这里简单返回openid生产环境应生成Token ctx.body { code: 200, message: 登录成功, data: { userId: user.id, openid: user.openid // 注意session_key 不应返回给前端 } }; } catch (error) { console.error(登录失败:, error); ctx.status 500; ctx.body { code: 500, message: 服务器内部错误 }; } } // 更新用户信息如昵称、头像 static async updateUserInfo(ctx) { const { userId, nickname, avatarUrl, phone } ctx.request.body; // 省略参数校验... try { await pool.query( UPDATE user SET nickname ?, avatar_url ?, phone ? WHERE id ?, [nickname, avatarUrl, phone, userId] ); ctx.body { code: 200, message: 更新成功 }; } catch (error) { ctx.status 500; ctx.body { code: 500, message: 更新失败 }; } } } module.exports UserController;4.4 实现商品列表与详情接口// 文件server/controller/productController.js const pool require(../utils/db); class ProductController { // 获取商品列表带分页和分类筛选 static async getList(ctx) { const { page 1, pageSize 10, categoryId, keyword } ctx.query; const offset (page - 1) * pageSize; let sql SELECT * FROM product WHERE status 1; let params []; if (categoryId) { sql AND category_id ?; params.push(categoryId); } if (keyword) { sql AND name LIKE ?; params.push(%${keyword}%); } sql ORDER BY create_time DESC LIMIT ? OFFSET ?; params.push(parseInt(pageSize), offset); try { const [products] await pool.query(sql, params); // 获取总数 let countSql SELECT COUNT(*) as total FROM product WHERE status 1; let countParams []; // ... 同样拼接分类和关键词条件 const [countResult] await pool.query(countSql, countParams); const total countResult[0].total; ctx.body { code: 200, data: { list: products, pagination: { page: parseInt(page), pageSize: parseInt(pageSize), total } } }; } catch (error) { ctx.status 500; ctx.body { code: 500, message: 查询失败 }; } } // 获取商品详情 static async getDetail(ctx) { const { id } ctx.params; try { const [products] await pool.query(SELECT * FROM product WHERE id ? AND status 1, [id]); if (products.length 0) { ctx.status 404; ctx.body { code: 404, message: 商品不存在 }; return; } ctx.body { code: 200, data: products[0] }; } catch (error) { ctx.status 500; ctx.body { code: 500, message: 查询失败 }; } } } module.exports ProductController;4.5 实现创建订单接口这是最复杂的业务接口之一涉及事务处理确保库存扣减和订单创建的一致性。// 文件server/controller/orderController.js const pool require(../utils/db); class OrderController { static async create(ctx) { const { userId, items, address, remark } ctx.request.body; // items: [{productId, quantity}] if (!userId || !items || !Array.isArray(items) || items.length 0 || !address) { ctx.status 400; ctx.body { code: 400, message: 参数错误 }; return; } const connection await pool.getConnection(); // 获取一个连接 await connection.beginTransaction(); // 开启事务 try { // 1. 生成订单号示例年月日时分秒4位随机数 const orderId ${Date.now()}${Math.floor(Math.random() * 10000)}.slice(0, 32); // 2. 计算总金额并验证商品和库存 let totalAmount 0; const orderItems []; for (const item of items) { const [products] await connection.query( SELECT id, name, price, stock FROM product WHERE id ? AND status 1 FOR UPDATE, // FOR UPDATE 行锁防止超卖 [item.productId] ); if (products.length 0) { throw new Error(商品ID ${item.productId} 不存在或已下架); } const product products[0]; if (product.stock item.quantity) { throw new Error(商品 ${product.name} 库存不足); } const itemTotal product.price * item.quantity; totalAmount itemTotal; orderItems.push({ orderId, productId: product.id, productName: product.name, productPrice: product.price, quantity: item.quantity, totalPrice: itemTotal }); // 预扣库存在事务中 await connection.query(UPDATE product SET stock stock - ? WHERE id ?, [ item.quantity, product.id ]); } // 3. 插入订单主表 await connection.query( INSERT INTO \order\ (id, user_id, total_amount, pay_amount, status, address, remark) VALUES (?, ?, ?, ?, 0, ?, ?), [orderId, userId, totalAmount, totalAmount, JSON.stringify(address), remark || ] ); // 4. 批量插入订单商品明细 const itemValues orderItems.map(item [ item.orderId, item.productId, item.productName, item.productPrice, item.quantity, item.totalPrice ]); await connection.query( INSERT INTO order_item (order_id, product_id, product_name, product_price, quantity, total_price) VALUES ?, [itemValues] ); // 5. 提交事务 await connection.commit(); connection.release(); ctx.body { code: 200, message: 订单创建成功, data: { orderId, totalAmount } }; } catch (error) { // 6. 发生错误回滚事务 await connection.rollback(); connection.release(); console.error(创建订单失败:, error); ctx.status 500; ctx.body { code: 500, message: 订单创建失败: ${error.message} }; } } } module.exports OrderController;4.6 配置路由与启动应用// 文件server/router/index.js const Router require(koa-router); const UserController require(../controller/userController); const ProductController require(../controller/productController); const OrderController require(../controller/orderController); const router new Router(); // 用户相关 router.post(/user/login, UserController.login); router.post(/user/update, UserController.updateUserInfo); // 商品相关 router.get(/product/list, ProductController.getList); router.get(/product/:id, ProductController.getDetail); // 订单相关 router.post(/order/create, OrderController.create); // 可以继续添加获取订单列表、订单详情等接口 module.exports router;// 文件server/app.js const Koa require(koa); const bodyParser require(koa-bodyparser); const router require(./router); const app new Koa(); const PORT process.env.PORT || 3000; // 中间件 app.use(bodyParser()); // 解析请求体 app.use(router.routes()).use(router.allowedMethods()); // 路由 app.listen(PORT, () { console.log(Server is running on http://localhost:${PORT}); });在package.json中添加启动脚本{ scripts: { start: node app.js, dev: nodemon app.js } }现在在server目录下运行npm run dev你的后端API服务就跑起来了。5. 微信小程序前端开发实战后端API准备好后我们开始编写小程序前端页面。5.1 项目配置与基础页面首先用微信开发者工具新建一个小程序项目选择空白模板并将项目目录指向我们之前创建的miniprogram文件夹。全局配置app.json:{ pages: [ pages/index/index, pages/category/category, pages/cart/cart, pages/my/my, pages/product/detail, pages/order/confirm, pages/order/list, pages/address/list ], window: { backgroundTextStyle: light, navigationBarBackgroundColor: #ff5a5f, navigationBarTitleText: 校园外卖, navigationBarTextStyle: white }, tabBar: { color: #999, selectedColor: #ff5a5f, list: [ { pagePath: pages/index/index, text: 首页, iconPath: images/tabbar/home.png, selectedIconPath: images/tabbar/home_active.png }, { pagePath: pages/category/category, text: 分类, iconPath: images/tabbar/category.png, selectedIconPath: images/tabbar/category_active.png }, { pagePath: pages/cart/cart, text: 购物车, iconPath: images/tabbar/cart.png, selectedIconPath: images/tabbar/cart_active.png }, { pagePath: pages/my/my, text: 我的, iconPath: images/tabbar/my.png, selectedIconPath: images/tabbar/my_active.png } ] }, style: v2, sitemapLocation: sitemap.json }5.2 封装网络请求工具创建一个统一的请求工具便于管理接口基地址、token等。// 文件miniprogram/utils/request.js const BASE_URL http://localhost:3000; // 你的后端地址上线需改为HTTPS域名 const request (url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { content-type: application/json, // 可以从本地存储中读取token Authorization: wx.getStorageSync(token) || }, success(res) { if (res.statusCode 200) { resolve(res.data); } else { reject(res.data); } }, fail(err) { reject(err); wx.showToast({ title: 网络请求失败, icon: none }); } }); }); }; // 导出常用的方法 module.exports { get: (url, data) request(url, GET, data), post: (url, data) request(url, POST, data), put: (url, data) request(url, PUT, data), delete: (url, data) request(url, DELETE, data) };5.3 实现首页商品列表首页通常展示轮播图、分类入口和商品列表。!-- 文件miniprogram/pages/index/index.wxml -- view classcontainer !-- 搜索框 -- view classsearch-bar input placeholder搜索商品 bindinputonSearchInput / /view !-- 轮播图 -- swiper indicator-dots autoplay interval3000 swiper-item wx:for{{bannerList}} wx:keyid image src{{item.imageUrl}} modeaspectFill / /swiper-item /swiper !-- 分类入口 -- scroll-view scroll-x classcategory-scroll view classcategory-item wx:for{{categoryList}} wx:keyid bindtaponCategoryTap>// 文件miniprogram/pages/index/index.js const request require(../../utils/request.js); Page({ data: { bannerList: [], // 轮播图数据 categoryList: [], // 分类数据 productList: [], // 商品列表 page: 1, pageSize: 10, hasMore: true, keyword: }, onLoad() { this.loadBanner(); this.loadCategory(); this.loadProducts(); }, // 加载轮播图 async loadBanner() { // 这里可以调用后端接口或写死本地数据 this.setData({ bannerList: [ { id: 1, imageUrl: /images/banner1.jpg }, { id: 2, imageUrl: /images/banner2.jpg } ] }); }, // 加载分类 async loadCategory() { // 同上可以调用接口 this.setData({ categoryList: [ { id: 1, name: 快餐便当, icon: /images/category1.png }, { id: 2, name: 奶茶饮品, icon: /images/category2.png } ] }); }, // 加载商品 async loadProducts(isLoadMore false) { if (!isLoadMore) { this.setData({ page: 1, productList: [] }); } const { page, pageSize, keyword } this.data; wx.showLoading({ title: 加载中... }); try { const res await request.get(/product/list, { page, pageSize, keyword }); if (res.code 200) { const newList isLoadMore ? this.data.productList.concat(res.data.list) : res.data.list; this.setData({ productList: newList, hasMore: newList.length res.data.pagination.total }); if (isLoadMore) { this.setData({ page: page 1 }); } } } catch (error) { console.error(加载商品失败:, error); wx.showToast({ title: 加载失败, icon: none }); } finally { wx.hideLoading(); } }, // 搜索输入 onSearchInput(e) { this.setData({ keyword: e.detail.value }); // 可以加防抖这里简单处理 clearTimeout(this.timer); this.timer setTimeout(() { this.loadProducts(); }, 500); }, // 点击分类 onCategoryTap(e) { const categoryId e.currentTarget.dataset.id; wx.navigateTo({ url: /pages/category/category?id${categoryId} }); }, // 点击商品 onProductTap(e) { const productId e.currentTarget.dataset.id; wx.navigateTo({ url: /pages/product/detail?id${productId} }); }, // 加载更多 loadMore() { if (this.data.hasMore) { this.loadProducts(true); } } });5.4 实现购物车功能购物车状态通常使用小程序的全局数据管理如getApp().globalData或状态管理库这里为了简单使用页面内的 data 和本地存储。// 文件miniprogram/pages/cart/cart.js Page({ data: { cartList: [], selectedAll: false, totalPrice: 0 }, onShow() { this.loadCartData(); }, // 从本地存储加载购物车 loadCartData() { const cartList wx.getStorageSync(cart) || []; this.calculate(cartList); this.setData({ cartList }); }, // 计算总价和全选状态 calculate(list) { let totalPrice 0; let selectedAll true; for (const item of list) { if (item.selected) { totalPrice item.price * item.quantity; } else { selectedAll false; } } this.setData({ totalPrice, selectedAll }); }, // 数量增加 onIncrease(e) { const index e.currentTarget.dataset.index; const cartList this.data.cartList; cartList[index].quantity 1; this.updateCart(cartList); }, // 数量减少 onDecrease(e) { const index e.currentTarget.dataset.index; const cartList this.data.cartList; if (cartList[index].quantity 1) { cartList[index].quantity - 1; this.updateCart(cartList); } }, // 选择商品 onSelectItem(e) { const index e.currentTarget.dataset.index; const cartList this.data.cartList; cartList[index].selected !cartList[index].selected; this.updateCart(cartList); }, // 全选/取消全选 onSelectAll() { const selectedAll !this.data.selectedAll; const cartList this.data.cartList.map(item ({ ...item, selected: selectedAll })); this.updateCart(cartList); }, // 更新购物车数据并保存到本地 updateCart(cartList) { wx.setStorageSync(cart, cartList); this.calculate(cartList); this.setData({ cartList }); }, // 去结算 onCheckout() { const selectedItems this.data.cartList.filter(item item.selected); if (selectedItems.length 0) { wx.showToast({ title: 请选择商品, icon: none }); return; } // 将选中的商品信息传递到确认订单页面 wx.navigateTo({ url: /pages/order/confirm?items${encodeURIComponent(JSON.stringify(selectedItems))} }); } });6. 常见问题与排查思路在开发过程中你可能会遇到以下问题问题现象常见原因解决思路微信开发者工具无法连接本地后端1. 后端服务未启动。2. 小程序设置了域名校验未将本地IP加入白名单。1. 检查npm run dev是否成功端口是否被占用。2. 在微信开发者工具详情 - 本地设置中勾选“不校验合法域名...”仅开发环境。上线前必须在微信公众平台配置服务器域名。调用wx.login成功但后端换openid失败1.appid和secret配置错误。2. 服务器网络问题无法访问微信API。3.code被重复使用或已过期。1. 检查.env文件中的WX_APPID和WX_SECRET是否正确。2. 确保后端服务器能访问外网。3. 确保前端每次登录都获取新的code。数据库连接失败1. MySQL服务未启动。2. 连接配置主机、端口、用户名、密码、数据库名错误。3. 用户权限不足。1. 检查MySQL服务状态。2. 逐一核对.env中的数据库配置。3. 使用命令行或工具测试用该用户能否连接并操作目标数据库。创建订单时出现超卖多个用户同时下单同一商品查询库存和扣减库存不是原子操作。使用数据库事务 SELECT ... FOR UPDATE行锁或使用 Redis 分布式锁或使用乐观锁版本号。本文示例使用了事务行锁。小程序真机预览时图片不显示1. 图片路径错误。2. 图片域名未配置到微信后台。3. 图片服务器不支持 HTTPS。1. 检查图片路径是相对路径还是网络URL。2. 网络图片的域名必须在小程序后台的downloadFile合法域名中配置。3. 小程序要求网络请求必须是 HTTPS。支付功能无法调起1. 商户号、API密钥配置错误。2. 统一下单接口返回预支付ID失败。3. 前端调起支付参数签名错误。1. 仔细核对微信支付商户平台的配置。2. 查看统一下单接口的返回信息常见错误是“商户号与appid不匹配”。3. 确保后端生成支付参数和前端调起支付的签名算法一致。7. 最佳实践与工程建议将项目推向生产环境时需要考虑更多工程化问题。7.1 安全性接口鉴权本文示例简单返回了openid生产环境必须使用 Token如 JWT机制。用户登录后后端生成一个 Token 返回给前端前端后续请求在 Header 中携带此 Token后端进行验证。SQL 注入务必使用参数化查询如?占位符就像我们示例中使用mysql2的query方法那样绝对不要拼接 SQL 字符串。敏感信息保护appid、secret、数据库密码、支付密钥等必须通过环境变量或配置中心管理绝不能硬编码在代码中或提交到代码仓库。输入验证对所有用户输入如订单备注、地址进行严格的校验和过滤防止 XSS 攻击。HTTPS生产环境必须使用 HTTPS小程序强制要求。7.2 性能优化数据库优化为常用的查询字段建立索引如product表的category_id,status。避免SELECT *只查询需要的字段。对大数据量表进行分库分表考虑如订单表按时间分表。API 优化对商品列表、分类等不常变的数据使用 Redis 缓存。接口设计时考虑前端需求避免多次请求如商品详情页可一次返回商品信息和评价。使用分页避免一次性拉取大量数据。小程序优化图片使用 CDN 并压缩。合理使用分包加载减少主包体积。使用wx:if和hidden控制渲染避免不必要的节点渲染。7.3 可维护性代码结构保持清晰的分层Controller, Service, Model单一职责。错误处理建立统一的错误处理中间件规范化错误码和错误信息返回。日志记录使用winston、log4js等库记录关键业务日志和错误日志便于排查问题。配置管理将不同环境开发、测试、生产的配置分离。API 文档使用 Swagger 或 Apifox 等工具维护接口文档方便前后端协作。7.4 部署上线后端部署可以选择云服务器如腾讯云、阿里云ECS使用 PM2 进程管理工具来守护 Node.js 进程。更现代的做法是使用 Docker 容器化部署。数据库部署建议使用云数据库服务如腾讯云CDB、阿里云RDS它们提供了高可用、备份和监控。小程序上线在微信公众平台提交代码审核。配置服务器域名request 合法域名、uploadFile 合法域名等。配置业务域名如 web-view 组件指向的H5页面。域名与SSL为你的后端服务申请域名并配置 SSL 证书可以使用 Let‘s Encrypt 免费证书。通过以上步骤你已经掌握了一个微信小程序外卖商城平台从设计到实现的核心流程。从数据库设计、后端API开发到前端页面交互每个环节都提供了可运行的代码示例和关键逻辑解释。项目源码已整理好你可以基于此进行二次开发添加更多功能如优惠券、拼单、评论系统等。在实际开发中耐心调试和查阅官方文档是解决问题的关键。如果在实践中遇到新的问题欢迎在评论区交流讨论。
返回列表