首页 / 资讯中心 / 文章详情

微信小程序+ThinkPHP6商城快速部署指南

微信小程序+ThinkPHP6商城快速部署指南 ★ FEATURED ARTICLE
简介这是一套面向Web开发初学者与中小型电商项目实践者的微信小程序商城全栈解决方案涵盖小程序前端、ThinkPHP后端及完整部署文档解决从零搭建可运行商城系统的实际需求。资源共2001个文件主体为629个PHP后端逻辑文件、438个HTML页面模板、309个JS交互脚本及89个CSS样式文件辅以311个GIF动效与21个PNG图标资源整体压缩包44.4MB结构清晰、模块分离明确。已有533人学习下载适合希望快速掌握小程序PHP协同开发、理解电商核心流程商品展示、购物车、订单闭环、用户管理的开发者。读者可直接复用前后端代码结合详尽的配置指南、数据库建表SQL、API接口说明及常见问题排错提示高效完成本地环境搭建与功能验证无需额外查找碎片化资料。1. 为什么“简易商城微信小程序 ThinkPHP后端”不是拼凑概念而是中小团队能当天跑通的最小闭环你手头有一份标着「简易商城微信小程序源码 ThinkPHP后端完整版 搭建教程」的压缩包解压后看到miniprogram/、thinkphp/、docs/三个文件夹——这不是模板站卖的“伪全栈”而是真实可部署、可调试、可改价改商品、可走通用户下单→后台管理→数据库写入全流程的最小生产级组合。它解决的不是“怎么学小程序”而是“明天客户要上线一个能收款的轻量商城今天下午能不能搭出来”。核心价值在于前端用原生微信小程序框架非 uni-app、非 Taro后端用 ThinkPHP 6.0LTS 版本非 TP5 旧版两者通过标准 RESTful 接口通信所有接口路径、Token 验证逻辑、数据库字段映射都已对齐连微信支付回调验签和订单状态同步都预埋了占位逻辑。适合刚接手外包项目的技术负责人、想快速验证电商 MVP 的创业者、或需要给学员交付“真能跑”的实训案例的讲师。它不追求高并发或秒杀但把「登录 → 获取手机号 → 浏览商品 → 加入购物车 → 提交订单 → 后台查看订单」这条主链路上每个环节的脏数据校验、空值兜底、错误码返回都写死了——这才是“简易”二字的真实含义删掉所有炫技代码只留业务主干上最硬的那几根骨头。2. 从零部署三步跑通本地开发环境含 PHP 环境避坑清单2.1 本地运行 ThinkPHP 后端Nginx PHP 8.0 MySQL 5.7 是黄金组合ThinkPHP 6.0 要求 PHP ≥ 7.3但实测 PHP 8.0.28 是目前最稳版本TP6.1 对 8.1 的部分反射机制有兼容问题。不要用 XAMPP/MAMP 一键包——它们默认开启opcache.enable0且date.timezone未设会导致 TP 的日志写入失败和时间戳错乱。我一般用 Docker Compose 快速拉起干净环境# docker-compose.yml version: 3.8 services: nginx: image: nginx:1.22-alpine ports: [8080:80] volumes: - ./thinkphp:/var/www/html - ./nginx.conf:/etc/nginx/conf.d/default.conf depends_on: [php, mysql] php: image: php:8.0-fpm-alpine volumes: - ./thinkphp:/var/www/html - ./php.ini:/usr/local/etc/php/php.ini environment: - TZAsia/Shanghai mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: shop_db volumes: - ./mysql-data:/var/lib/mysql注意nginx.conf中必须配置fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;否则 TP 的路由入口会 404php.ini里务必开启extensionmysqli和extensionpdo_mysqlTP 的数据库连接器依赖这两个扩展。启动后访问http://localhost:8080若看到 ThinkPHP 默认欢迎页说明基础环境通了。接着执行数据库迁移cd thinkphp php think migrate:run php think seed:run这两条命令会自动创建shop_goods、shop_orders、shop_users等 7 张表并插入测试商品和管理员账号账号 admin / 密码 123456。关键点migrate文件在thinkphp/database/migration/下seed数据在thinkphp/database/seed/它们是按 TP 官方规范写的不是手写 SQL —— 这意味着你后续加字段只需php think make:migration add_stock_to_goods再改up()方法即可不用手动进 MySQL。2.2 微信小程序开发者工具导入与域名配置打开微信开发者工具v1.06.2312190 或更新选择「导入项目」→ 指向miniprogram/目录。此时会报错request:fail url not in domain list。这是因为小程序强制要求 HTTPS 域名而本地localhost:8080是 HTTP。解决方案分两步临时开发绕过在开发者工具右上角「详情」→「本地设置」→ 勾选「不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书」。仅限开发阶段使用上线前必须关闭接口域名绑定在miniprogram/app.js中找到config.apiBase https://your-domain.com将其改为你的测试域名如http://192.168.1.100:8080同时在project.config.json的setting节点下添加urlCheck: false, es6: true, enhance: true, nodeModulesPath: ./node_modules提示miniprogram/utils/request.js封装了统一请求拦截器所有 API 调用都走这里。它自动在 header 中注入Authorization: Bearer ${token}并全局处理 401跳转登录、500Toast 提示。你不需要在每个页面重复写wx.request。2.3 前后端联调用 Postman 验证核心接口链路别急着在小程序里点按钮先用 Postman 把主链路接口串起来接口方法URL参数预期响应用户登录POST/api/v1/auth/login{ code: 012abc... }微信 login code{ token: eyJhb..., user_id: 123 }获取手机号POST/api/v1/auth/get-phoneheader:Authorization: Bearer xxx, body:{ encryptedData: ..., iv: ... }{ phone: 138****1234 }获取商品列表GET/api/v1/goods?limit10offset0无[ { id: 1, name: iPhone 15, price: 5999 } ]创建订单POST/api/v1/orders{ goods_id: 1, count: 1, address_id: 5 }{ order_no: ORD20240520123456, pay_url: https://pay.weixin.qq.com/... }验证逻辑先调login拿 token → 用该 token 调get-phone→ 再调goods看数据 → 最后用orders提交一笔测试单。如果全部返回 200 且数据结构匹配miniprogram/types/api.d.ts中定义的 interface说明前后端契约完全对齐。这是比直接跑小程序更高效的排错方式——因为错误信息在 Postman 里是明文 JSON而在小程序里可能只显示“请求失败”。3. 微信登录与手机号获取不是调 API 就完事关键在服务端验签逻辑3.1 小程序端wx.login()wx.getUserProfile()的正确时序很多新手把wx.login()和wx.getUserProfile()合并在一个按钮里触发结果用户点一次弹两个授权框体验极差。正确做法是分两步首次进入自动静默登录在app.js的onLaunch中调wx.login()拿到code后立即发给后端/api/v1/auth/login换取token并存入wx.setStorageSync(token, token)用户主动授权手机号在「我的」页面或结算页放一个button open-typegetPhoneNumber点击后触发getPhoneNumber事件拿到encryptedData和iv再 POST 到/api/v1/auth/get-phone。// miniprogram/pages/user/index.js onGetPhoneNumber(e) { if (e.detail.code) { wx.showLoading({ title: 绑定中... }); wx.request({ url: getApp().globalData.apiBase /api/v1/auth/get-phone, method: POST, header: { Authorization: Bearer wx.getStorageSync(token) }, data: { encryptedData: e.detail.encryptedData, iv: e.detail.iv }, success: (res) { if (res.data.phone) { wx.showToast({ title: 绑定成功, icon: success }); this.setData({ phone: res.data.phone }); } } }); } }注意open-typegetPhoneNumber按钮必须是button标签且不能加bindtap否则微信不会触发回调e.detail.code是空字符串时代表用户拒绝授权需引导重新点击。3.2 ThinkPHP 后端验签用openssl_public_decrypt解密手机号非 base64_decode微信返回的encryptedData是 AES-128-CBC 加密的二进制数据不能直接 base64_decode。TP 后端必须用openssl_public_decrypt配合微信公钥解密。源码中thinkphp/app/controller/v1/Auth.php的getPhone()方法已实现public function getPhone() { $data $this-request-post(); $encryptedData base64_decode($data[encryptedData]); $iv base64_decode($data[iv]); // 获取微信公钥从微信开放平台下载存为 public_key.pem $publicKey file_get_contents(Env::get(root_path) . config/wx_public_key.pem); $decrypted ; if (openssl_public_decrypt($encryptedData, $decrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING)) { $phoneInfo json_decode($decrypted, true); return $this-success([phone $phoneInfo[phoneNumber]]); } else { return $this-error(手机号解密失败请重试); } }关键参数说明OPENSSL_PKCS1_OAEP_PADDING微信强制要求的填充方式用OPENSSL_PKCS1_PADDING会解密失败wx_public_key.pem必须从微信开放平台「开发管理 → 开发者工具 → 公钥下载」获取不是小程序后台的 AppID 秘钥$phoneInfo[phoneNumber]解密后 JSON 的 key 是phoneNumber注意大小写不是phone或mobile。3.3 为什么「获取手机号」接口必须带 Token 鉴权有人会问既然getPhoneNumber是微信原生组件为什么还要传 Token因为微信只保证encryptedData是当前用户加密的但不保证请求来自你的合法小程序中间人可截获encryptedData重放Token 是你后端颁发的 session 凭证绑定用户 ID 和设备指纹防止恶意调用getPhone()方法开头有$this-checkAuth();会校验 Token 是否有效、是否过期、是否被注销。血泪经验曾有个项目没做 Token 校验攻击者用抓包工具反复提交同一份encryptedData导致同一个手机号被绑定到 100 个账号。加上鉴权后此类风险归零。4. 商品与订单模块数据库设计与 TP 模型关联的落地细节4.1 四张核心表的关系与字段陷阱附建表 SQL商城最易翻车的是商品、规格、库存、订单四张表的关联逻辑。源码中shop_goods、shop_sku、shop_stock、shop_orders的设计遵循「一对多 多对多」原则但有几个隐藏坑-- shop_goods商品主表SPU CREATE TABLE shop_goods ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 商品名称, cover varchar(255) NOT NULL COMMENT 封面图, price decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 售价, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态0下架1上架, PRIMARY KEY (id) ); -- shop_sku规格表SKU CREATE TABLE shop_sku ( id int(11) NOT NULL AUTO_INCREMENT, goods_id int(11) NOT NULL COMMENT 关联商品ID, spec varchar(50) NOT NULL COMMENT 规格描述如黑色 128G, price decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 规格价, PRIMARY KEY (id), KEY idx_goods_id (goods_id) ); -- shop_stock库存表独立于 SKU支持多仓 CREATE TABLE shop_stock ( id int(11) NOT NULL AUTO_INCREMENT, sku_id int(11) NOT NULL COMMENT 关联规格ID, warehouse varchar(20) NOT NULL DEFAULT default COMMENT 仓库编码, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存数量, PRIMARY KEY (id), KEY idx_sku_warehouse (sku_id,warehouse) ); -- shop_orders订单主表 CREATE TABLE shop_orders ( id int(11) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id int(11) NOT NULL COMMENT 用户ID, total_amount decimal(10,2) NOT NULL COMMENT 总金额, status tinyint(1) NOT NULL DEFAULT 0 COMMENT 状态0待支付1已支付2已发货3已完成, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) );关键设计点shop_sku.price允许为空表示继承shop_goods.price前端展示时需判断sku.price ?: goods.priceshop_stock.warehouse默认default方便后期扩展区域仓如shanghai、guangzhoushop_orders.status用整型而非字符串避免status paid这类低效查询。4.2 ThinkPHP 模型关联用hasManyThrough查订单商品详情用户在「我的订单」页要显示每笔订单的商品图、名称、规格、单价、数量。如果用 N1 查询先查订单再 foreach 查商品10 笔订单会发 11 条 SQL。源码用hasManyThrough一次性关联// thinkphp/app/model/Order.php class Order extends Model { protected $table shop_orders; public function goods() { return $this-hasManyThrough( Goods::class, // 最终要查的模型 OrderItem::class, // 中间表模型 order_id, // 中间表外键指向 Order goods_id, // Goods 表主键指向 Goods id, // Order 表主键 id // OrderItem 表主键 ); } } // thinkphp/app/model/OrderItem.php中间表 class OrderItem extends Model { protected $table shop_order_items; }调用时Order::with(goods)-find(123)TP 自动生成 JOIN 查询一条 SQL 返回订单 所有商品信息。参数说明第 3 个参数order_idOrderItem表中关联Order的字段名第 4 个参数goods_idOrderItem表中关联Goods的字段名第 5、6 个参数id分别是Order和Goods的主键名默认就是id可省略。4.3 小程序端购物车持久化用wx.setStorageSync存数组而非云存储很多教程教用云开发存购物车但本源码坚持用本地缓存——因为「简易商城」的核心是降低部署复杂度。miniprogram/utils/cart.js实现了原子化操作const CART_KEY cart_items; export const addToCart (goodsId, skuId, count 1) { let cart wx.getStorageSync(CART_KEY) || []; const exist cart.find(item item.goods_id goodsId item.sku_id skuId); if (exist) { exist.count count; } else { cart.push({ goods_id: goodsId, sku_id: skuId, count }); } wx.setStorageSync(CART_KEY, cart); }; export const getCartItems () { return wx.getStorageSync(CART_KEY) || []; };为什么不用云存储云开发需额外开通环境、配置安全规则、处理并发写冲突本地缓存setStorageSync在 iOS 上有 10MB 限制但购物车数据极小单条 1KB1000 件也才 1MB用户换手机时购物车丢失是可接受代价毕竟「简易商城」定位是快速验证非生产级长周期运营。5. 避坑指南这 4 个错误让 80% 的新手卡在部署第二天5.1 现象小程序调login接口返回{code:500,msg:cURL error 6: Could not resolve host: api.weixin.qq.com}原因ThinkPHP 后端调用微信jscode2session接口时服务器 DNS 解析失败。常见于 Docker 容器内未配置 DNS或阿里云 ECS 安全组屏蔽了 443 端口出方向。解决在thinkphp/config/app.php中增加超时和 DNS 设置curl [ timeout 10, dns_cache_timeout 300, dns_servers [8.8.8.8, 114.114.114.114], ],并在docker-compose.yml的php服务下添加dns: - 8.8.8.8 - 114.114.114.1145.2 现象后台管理页空白控制台报Uncaught SyntaxError: Unexpected token 原因Nginx 配置未将/admin/*路由全部指向index.php导致访问/admin/login时返回了 HTML即 TP 的 404 页面但浏览器当 JS 解析。解决修改nginx.conf的location /admin/块location /admin/ { try_files $uri $uri/ /admin/index.php?$query_string; }并确保thinkphp/public/admin/index.php存在源码已内置但部分压缩包漏放。5.3 现象微信支付回调无日志订单状态不更新原因微信支付回调地址https://your-domain.com/api/v1/pay/notify未在微信商户平台「产品中心 → APPID 账号绑定」中配置或服务器未开放 443 端口。解决登录微信商户平台 → 「开发配置 → 支付配置 → 回调URL」填入完整 HTTPS 地址在thinkphp/app/controller/v1/Pay.php的notify()方法开头加日志Log::write(Pay notify received: . file_get_contents(php://input), pay);检查服务器防火墙sudo ufw allow 443Ubuntu或sudo firewall-cmd --permanent --add-port443/tcpCentOS。5.4 现象小程序上传图片到cloud://失败提示errCode: -1原因源码中miniprogram/utils/upload.js默认使用wx.cloud.uploadFile但未初始化云开发环境。而本项目实际用的是传统服务器上传/api/v1/upload。解决删除app.js中wx.cloud.init()调用确保miniprogram/utils/upload.js使用wx.uploadFileexport const uploadImage (filePath) { return new Promise((resolve, reject) { wx.uploadFile({ url: getApp().globalData.apiBase /api/v1/upload, filePath, name: file, success: (res) { const data JSON.parse(res.data); resolve(data.url); // 返回服务器保存的路径如 /uploads/20240520/abc.jpg } }); }); };6. 进阶技巧用「订单状态机」替代 if-else让售后流程可追溯、可审计6.1 为什么硬编码状态流转会失控原始订单状态用if ($order-status 0) { $order-status 1; }这类逻辑半年后加「退款中」「已退款」状态时所有updateStatus()方法都要改极易漏掉某个分支。源码引入状态机模式核心在thinkphp/app/service/OrderService.phpclass OrderService { // 状态流转规则key 是当前状态value 是允许转入的状态数组 private $transitions [ 0 [1], // 待支付 → 已支付 1 [2, 4], // 已支付 → 已发货 / 申请退款 2 [3], // 已发货 → 已完成 4 [5, 6], // 申请退款 → 退款中 / 已退款 ]; public function updateStatus($orderId, $toStatus) { $order Order::find($orderId); $fromStatus $order-status; if (!in_array($toStatus, $this-transitions[$fromStatus] ?? [])) { throw new \Exception(状态 {$fromStatus} 不允许转为 {$toStatus}); } // 记录状态变更日志 OrderLog::create([ order_id $orderId, from_status $fromStatus, to_status $toStatus, operator Auth::id(), remark 系统自动更新 ]); $order-status $toStatus; $order-save(); } }优势新增状态只需往$transitions数组加一行无需改业务逻辑OrderLog表记录每次变更客服查纠纷时可还原完整时间线throw new Exception让非法状态跳转会抛出明确错误前端可捕获并提示“操作不合法”。6.2 小程序端状态文案动态化用 Map 映射避免硬编码订单列表页显示「待支付」「已发货」等文字如果写死在 WXML 里后期翻译或多语言会很痛苦。源码用miniprogram/utils/status.js统一管理export const ORDER_STATUS_MAP { 0: { text: 待支付, color: #FF9900, btn: 去支付 }, 1: { text: 已支付, color: #00CC66, btn: 查看物流 }, 2: { text: 已发货, color: #0066CC, btn: 确认收货 }, 3: { text: 已完成, color: #999999, btn: }, 4: { text: 申请退款, color: #FF6600, btn: 查看进度 }, }; // WXML 中使用 view classstatus stylecolor: {{ ORDER_STATUS_MAP[order.status].color }} {{ ORDER_STATUS_MAP[order.status].text }} /view参数说明color直接用于内联样式避免额外 classbtn按钮文案空字符串表示不显示按钮所有状态值0/1/2...与数据库字段严格一致杜绝status pending这类字符串比较。6.3 真实场景如何用这套源码接私有化部署需求上周帮一家社区生鲜店部署他们要求所有数据不出内网后台只能用 iPad 访问订单打印到热敏打印机。我做了三处改造内网部署把docker-compose.yml中nginx的ports: [8080:80]改为ports: [192.168.1.100:80:80]仅绑定内网 IPiPad 适配在thinkphp/public/admin/css/style.css加媒体查询media (max-width: 1024px) { .sidebar { display: none; } .main-content { margin-left: 0; } }热敏打印在thinkphp/app/controller/v1/Order.php的printReceipt()方法中用escpos-php库生成 ESC/POS 指令$printer new Printer(new FilePrintConnector(/dev/usb/lp0)); $printer-text(订单号{$order-order_no}\n); $printer-cut(); $printer-close();这套源码的价值从来不是“功能多全”而是“边界清晰、改一处动一处、改完立刻见效”。我习惯把它当乐高积木——需要什么功能就拆开对应模块看逻辑再按自己业务缝回去。比如把「微信支付」换成「支付宝支付」只需改Pay.php的unifiedOrder()方法和前端pay.js的唤起逻辑其他订单、库存、用户模块完全不动。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站