兄弟们,手办这坑我是踩得明明白白。前阵子为了找一套能跑的商城源码,翻遍了各大论坛和代码仓库,要么是纯前端写死的假数据,要么后端接口烂得跟蜘蛛网一样。后来索性自己动手,用 Laravel 撸了一套“玩转潮玩”手办小程序,前后端全部打通,从商品展示到下单支付流程完整。文章末尾有获取方式,觉得有用的可以先点个关注,后续我还会持续迭代这套代码,分享更多实战改造过程。
那段时间我几乎把热词榜翻了个遍——“小程序抓包”、“charles使用教程”、“小程序商城”、“源码建站”这些词反复出现,看得出来太多人卡在前后端联调这一步了。这套程序刚好把小程序端和 Laravel 后端 API 完整做通了,数据结构、接口字段、状态码我都按实际生产环境的标准来设计,拿来即用,也方便二次开发。不管你是想做个副业项目接单,还是拿来练手学习小程序全栈开发,这套源码都能少走很多弯路。
1. 项目整体设计与技术选型思路
先说技术栈,后端选择 Laravel 这个框架,老实说是权衡之后的结果。市面上开源商城系统不少,Java 的若依、Python 的 Django 商城也有,但 PHP 生态在小程序后端这块有天然优势——部署简单、上手门槛低、虚拟主机都能跑。而 Laravel 又是 PHP 框架里最像“现代框架”的那个,ORM、中间件、队列、路由模型绑定都是现成的,开发效率确实比其他老牌框架高很多。试想一下,你用 ThinkPHP 写个接口得手动拼 SQL、手动做参数校验,Laravel 里写个 FormRequest 就搞定了,这对小团队和个人开发者来说省下的时间不是一点半点。
小程序端选微信原生框架而不是 uni-app,主要考虑是减少一层抽象。uni-app 确实能一套代码多端跑,但编译层偶尔会出一些莫名其妙的兼容问题,排查起来特别折磨人。微信原生框架写的代码逻辑最直白,遇到问题社区资料也多,毕竟做小程序开发绕不开微信这个平台,原生语法学扎实了,以后去哪个团队都不慌。
整个项目的模块划分是这样的:
- 用户端小程序:首页推荐、分类浏览、商品详情、购物车、订单管理、个人中心、收货地址管理;
- 管理后台(预留接口):商品管理、订单管理、用户管理、轮播图配置;
- 后端 API 服务:Laravel 提供 RESTful 接口,统一返回 JSON 格式数据。
关于数据库设计,我花了不少心思。商品表(products)和商品图片表(product_images)分开存,这样商品列表页加载缩略图时不用把详情大图也一块查出来,性能上的差异在数据量大了以后会非常明显。订单表(orders)和订单明细表(order_items)是标准的主从表关系,下单时把商品快照信息冗余进明细表里,这样即使以后商品改名或者下架,用户的历史订单依然能正确显示。
2. 核心功能模块与数据库设计
2.1 用户登录与鉴权机制
小程序端用户登录不能用传统的账号密码,必须走微信的 code 换 session_key 流程。这里我踩过坑,一开始直接拿前端传过来的 code 去调微信接口,后来发现问题在于 code 一次性有效,而且有效期只有五分钟。正确的做法是前端调用wx.login()拿到 code,传给后端,后端再用 code 去微信服务器换 openid 和 session_key,然后用自己的机制生成一个 token 返回给前端。
整个流程我用一张表来理清:
| 步骤 | 前端操作 | 后端处理 |
|---|---|---|
| 1 | wx.login() 获取 code | 接收 code |
| 2 | 请求 /api/auth/login | 用 code 调微信接口,获取 openid |
| 3 | 接收 token 并存到 storage | 查找用户,不存在则自动注册 |
| 4 | 后续请求头带上 token | 中间件校验 token 有效性 |
后端我用了 Laravel Sanctum 来做 API 鉴权,这个扩展比 JWT 好用,数据库表都不用自己建,执行一下迁移命令就自动生成 personal_access_tokens 表。每次请求带上 Bearer Token,中间件一拦,后面所有接口都能通过$request->user()拿到当前登录用户,代码写起来非常舒服。
2.2 商品模块与分类筛选
商品列表和详情是最核心的展示模块,直接决定用户的第一印象。列表页我设计了两种加载模式:首页推荐流和分类页列表。推荐流就是按创建时间倒序,取最新的商品,配合分页参数用cursor方式翻页;分类列表则支持按价格、销量、上架时间排序。
查询参数的设计可以参考本地:
// app/Http/Controllers/Api/ProductController.php public function index(Request $request) { $query = Product::query()->where('status', 1); if ($categoryId = $request->input('category_id')) { $query->where('category_id', $categoryId); } $sort = $request->input('sort', 'latest'); switch ($sort) { case 'sales': $query->orderByDesc('sales_count'); break; case 'price_asc': $query->orderBy('price'); break; case 'price_desc': $query->orderByDesc('price'); break; default: $query->orderByDesc('created_at'); } return $this->success($query->paginate(10)); }商品详情的接口我额外加了两个字段:is_favorite和favorite_count。这样前端在详情页能直接猜出用户有没有收藏过这个商品,不用额外再发一个请求去查收藏状态,省掉了一次网络往返,体验明显更顺滑。
商品图片我建议单独建表而不是用 JSON 字段存数组,原因是 JSON 字段在 MySQL 里做查询排序不够灵活,而且有些老版本的 MySQL 对 JSON 支持并不好。单独用一张表,每张图一行记录,查的时候用where('product_id', $id)->orderBy('sort_order')取出来,逻辑清晰还方便将来做图片懒加载。
2.3 购物车与订单流程设计
购物车这边我把数据存在后端,而不是用小程序本地缓存。这样做的考虑是:用户如果换了手机或者清掉微信缓存,购物车里的商品不会丢,对用户来说体验好很多。缺点是每次加购都要发一个请求,但说实话购物车的操作频次并不高,后端存储完全扛得住。
购物车表核心字段就这么几个:用户ID、商品ID、SKU ID、数量、勾选状态。这里要注意一个点:勾选状态一定要存,不然用户在结算页取消勾选某个商品后,再次进入购物车页面状态就乱了。
订单流程我走的是“预下单 → 确认收货地址 → 提交订单 → 模拟支付 → 支付成功”这条路。考虑到源码分发主要是学习交流用途,支付模块不建议直接接微信支付,因为个人主体的小程序很难申请到微信支付权限,而且涉及到商户号、证书密钥这些敏感信息,不适合放到开源代码里。我预留了PaymentService接口,注释写得很清楚,要接真实支付只需要实现pay()方法就行。
生成订单编号的函数值得分享一下,直接用uniqid()的做法不太专业,并发高一点就会出现重复单号。我用了日期 + 随机数 + 用户ID后三位组合的方式:
$orderNo = date('YmdHis') . str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT) . substr($userId, -3);这样同一个用户在同一秒内连下多单也不会冲突,而且通过单号能直接看出下单时间和用户信息,排查问题的时候特别方便。
2.4 管理端接口与数据统计
管理端我做了最基础的几个接口:商品上下架、订单发货、用户列表、销售统计。小程序管理后台我刻意没有做前端页面,因为市面上现成的后台管理模板太多了,大家拿到源码后可以按自己的喜好接入 vue-element-admin 或者若依前端,接口都是现成的。
销售统计这边我写了两个方法:今日订单数和今日销售额,另外再加一个近七天的走势图数据。统计的逻辑其实就是针对订单表的时间字段做条件筛选,加上whereDate('created_at', today()),Laravel 的查询构造器对这类操作支持得很顺手。
榜单相关词里反复出现“小程序动态设置标题”和“微信小程序顶部导航栏高度”,这个在我这个项目里也做了处理。用户浏览不同分类时,小程序导航栏标题会动态切换,这部分逻辑在小程序前端实现,代码注释里也有说明。
3. 小程序前端核心页面实现
3.1 首页与推荐流骨架
首页布局参考了目前主流电商小程序的通用模式:顶部搜索框、下方轮播图、金刚区图标、瀑布流商品推荐。我这里统一分为模板为首页、分类页、购物车、个人中心四个 Tab,底部导航栏在app.json里配置。
轮播图数据来自后端的/api/banners接口,直接在onLoad生命周期里请求,拿到数据后塞入data中。要注意的是请求时一定要处理好加载态,不然用户打开小程序看到一片空白会以为死掉了。我加了一个loading参数控制页面渲染状态,数据回来之前展示加载动画。
首页商品推荐流的实现要点是列表触底加载,也就是热词里说的“微信小程序页面列表加载更多”。逻辑是页面滚动到底部时触发onReachBottom方法,把当前页码加一再请求一次接口,新数据拼接到旧数据后面。为了避免重复数据,接口的分页参数我用page和page_size,前端记录page,每次请求前检查hasMore字段,为 false 就不再发起请求了。
3.2 商品详情页参数联动
详情页是我花心思最多的页面,因为它直接关系到转化率。轮播图、价格、标题、销量、收藏按钮、SKU 选择、图文详情、底部操作栏,每个模块都要做好。
SKU 选择的逻辑很多人一上来就懵,其实核心就一句话:传入被选中的规格,然后从后端查对应的价格和库存。比如一个手办有三个规格:普通版、豪华版、典藏版,每个版本价格不一样,用户点击某个版本就更新展示的价格。这里我把每个版本的库存独立存储,避免出现“总库存还有,但某个规格已经卖光”的尴尬。
加入购物车的接口调用前,前端要先做SKU的合法性校验。如果用户没选规格就直接点击“加入购物车”,直接弹 toast 提示“请选择规格”,这里的判断逻辑必须放在请求之前,否则后端就要多做一层防御性校验。不过作为一名严谨的开发者,后端的校验我照样写了一遍,两层保险。
3.3 购物车与结算页联动逻辑
购物车页面的核心难点在于“批量计算总价”。用户勾选、取消勾选、修改数量、删除商品,只要操作一个,右下角的总金额就要立即重算。我采用的做法是:前端维护一份购物车数据,每次变动都调用一个本地方法重新遍历所有商品项,只累加selected为 true 且checked为 true 的项,然后setData更新总金额。
结算页面更繁琐的地方在于优惠规则,这个源码里我只做了简单逻辑:满99元包邮。运费计算单独拆了一个方法calcShippingFee(),以后想改成“全场免邮”或者“满299减40”都很方便,直接调这个方法就行。
提交订单前需要校验三件事:商品库存是否充足、收货地址是否已填写、购物车勾选商品是否为空。这三个条件缺一个都要阻断提交流程并提示用户。有次测试的时候发现一个情况,用户把最后一件秒杀商品加购后没下单,过一会儿买了,这时候购物车是勾选状态,但库存已经不够了。所以提交订单时后端必须校验库存,并且要从数据库实时取,不能信前端传过来的参数。
3.4 个人中心与订单列表
个人中心的菜单项包括:我的订单、收货地址、优惠券(预留)、联系客服、关于我们。数据展示上,我用了热门词“小程序列表加载更多”提到的触底加载模式。订单列表的每条数据展示订单号、商品缩略图、订单状态、实付金额,点击进去能看到订单详情。
订单状态我用了数字字典:0待付款、1待发货、2待收货、3已完成、4已取消。前端拿到状态码后统一通过statusText映射成中文,这样后端不用返回冗余字段,前端也方便做多语言适配(虽然目前只有中文需求,但代码习惯要保持)。
收货地址管理这边用一个开源地区选择组件来实现省市区三级联动,数据源是china_area.json,大概几千条数据,打包进小程序后体积还能接受。选择完后把省市区名称和详细地址拼接存储,提交到后端的是完整地址字符串,这样后端不需要再做省市区表的联表查询,逻辑简单而且不容易出错。
4. 前后端联调与接口调试技巧
4.1 本地开发环境搭建与联调配置
很多人在本地跑小程序项目时,会遇到一个很头疼的问题:真机预览时请求不到本地后端接口。原因是真机上的localhost指向的是手机自己,而不是你的电脑。解决方案有两个:
第一,通过微信开发者工具的“不校验合法域名”选项关掉域名校验,然后在工具右上角的详情设置里找到本地设置,勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。开发模式下这样配置就可以用http://localhost:8000直接调试。
第二,如果手机和电脑在同一个局域网,可以用电脑的局域网 IP + Laravel 的开发服务器端口来请求接口,比如http://192.168.1.100:8000。这里需要在 Laravel 的config/sanctum.php里把stateful数组加上你这台电脑的 IP,否则跨域请求会被拦下来。
CORS 跨域问题也是联调时绕不开的一环。Laravel 8 之后的版本自带了一个HandleCors中间件,在app/Http/Kernel.php里已经默认注册了。你需要做的就是到config/cors.php里把allowed_origins改成['*'](开发环境),或者填上你的小程序域名。这里有个小细节:小程序请求后端接口时,Origin头是https://servicewechat.com/xxx,如果你的allowed_origins没有这个域名,请求就会失败。
4.2 抓包调试的正确姿势
榜单里热词“小程序抓包”、“charles使用教程”排得很靠前,看得出大家在这块需求很强烈。说实话,小程序抓包和网页抓包不太一样,主要原因是微信开发者工具内置了网络调试功能,在 Debug 模式下直接能看到每一个请求的 URL、请求头、响应体,其实不需要额外抓包工具。
但如果你想抓真机上的小程序请求,就需要用到 Charles 了。步骤稍微有点多,但跟着操作一遍就能记住:
- 电脑上打开 Charles,菜单栏选择 Proxy -> SSL Proxying Settings,勾选 Enable SSL Proxying,点击 Add 添加主机
*.servicewechat.com和*.qq.com; - 手机和电脑连同一个 Wi-Fi,手机 Wi-Fi 设置里把代理改成手动,服务器填电脑的局域网 IP,端口填 Charles 默认的 8888;
- 手机浏览器访问
chls.pro/ssl下载并安装 Charles 的根证书(iOS 需要额外信任证书); - 在 Charles 里右键想要看的接口,选择 Save Response 就能把返回的 JSON 存下来分析。
这里一定不要被热词中某些内容误导,抓包本身是本地开发调试手段,大家正常用于联调即可。我更推荐直接用微信开发者工具的 Network 面板,既省事又安全,数据一目了然。真机调试的必要性在于:一些接口在工具预览正常但真机上可能因为代理问题、网速问题表现不一致,这时候用真机调试加 Console 日志定位最靠谱。
4.3 常见接口联调问题排查
联调阶段最容易碰到的几类问题,我统一整理成一张速查表:
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 请求 404 | 路由未定义或参数绑定错误 | 检查api.php里的路由定义,确认参数名一致 |
| 请求 500 | Laravel 代码报错 | 打开storage/logs/laravel.log查看错误栈 |
| 请求被 CORS 拦截 | 跨域配置不完整 | 检查config/cors.php,放行对应 Origin |
| 返回数据为 null | 模型关联写错 | 检查 model 里hasMany/belongsTo定义 |
| 真机请求失败 | 域名未校验 | 开发者工具勾选不校验域名,或配置合法域名 |
| 接口响应慢 | 导数据了没有加索引 | 给外键字段加索引,避免全表扫描 |
有个坑必须提醒:用 Laravel 自带的开发服务器php artisan serve在本地跑,遇到并发请求时会出现假死现象,这是因为 PHP 内置的服务器是单线程的。开发环境建议装个 Laragon 或者直接用 Docker 跑 Nginx + PHP-FPM,这样才会接近生产环境的表现。
5. 部署上线与后端配置实践
5.1 服务器环境要求与配置
部署这套系统到生产环境,推荐配置是 Nginx + PHP 8.1+ + MySQL 5.7+。PHP 版本千万别用太旧的,Laravel 9 以上的版本要求 PHP 8.0 起步,用 7.4 跑会直接报错。另一个坑是 PHP 的扩展,php -m命令确认一下有没有fileinfo、openssl、pdo_mysql,缺一个都起不来。
Nginx 配置 Laravel 项目的核心是try_files指令,官方配置长这样:
server { listen 80; server_name your-domain.com; root /var/www/your-project/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; index index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; } }域名配置好之后,再到小程序后台把服务器域名加进去。这里要注意一个细节:微信要求请求域名必须是 HTTPS,而且证书必须有效,在“开发管理 → 开发设置 → 服务器域名”里分别配置 request 合法域名、uploadFile 合法域名、downloadFile 合法域名。downloadFile 域名容易被忽略,商品图片传到 CDN 后,如果小程序里要用<image>标签展示,那 CDN 域名必须加入 downloadFile 合法列表,否则图片会显示不出来。
5.2 .env 配置与安全注意事项
Laravel 项目的关键配置都在.env文件里,部署后第一时间要修改以下内容:
APP_ENV=production,把开发模式关掉,避免错误信息直接暴露给用户;APP_DEBUG=false,这个一定要改,APP_DEBUG=true的时候,任何报错都会把文件路径和 SQL 语句直接打到页面上,等于把一个完整的信息泄露接口暴露给攻击者;DB_*数据库配置改成生产库,数据库密码用强密码;APP_KEY要重新生成,命令是php artisan key:generate。
另外强烈建议把.env文件加入.gitignore,不然传到代码仓库里就老老实实把你的数据库密码、微信 AppSecret 这些敏感信息公之于众了。之前见过不少开源项目拉下来之后.env还躺在目录里,这种操作属于安全事故级别的问题了。
5.3 微信小程序上线审核要点
小程序开发完提审之前,有几件事必须确认,否则白白浪费几天审核时间:
第一,用户隐私协议必须完善。微信要求小程序必须说明收集了哪些用户信息以及用途。我这里的登录接口就收集了用户的 openid,必须在小程序后台的“用户隐私保护指引”里声明确认。
第二,类目要选对。“玩转潮玩”这种手办商城,类目应该选择“电商平台”或者“商家自营”,如果没有对应资质需要补充。个人主体注册的账号做不了电商类目,这是硬性限制。
第三,虚拟支付的问题要避雷。微信对虚拟商品的支付管控极严,如果卖的是虚拟物品(比如电子卡、积分),微信支付额度会被冻结甚至封号。所以我这套源码默认走的是模拟支付,就是为了规避这个问题。上线后真正接入微信支付,销售的商品必须是实物手办。
审核被拒最常见的理由有:页面空白(接口没通)、类目不符合、没有客服入口、没有运营者联系方式。提交审核前自己最好走一遍核心流程,从登录到商品浏览到下单,确认每一步都顺畅,能少走很多弯路。
6. 源码获取与后续规划
关于获取方式,直接看个人主页简介或者文章评论区置顶,代码我会打包成完整的工程文件,包括数据库文件和部署文档。既然是开源分享,我自己用的也是宽松的 MIT 协议,也就是说你可以随意修改、商用,甚至把它改成你的毕业设计或者接单项目(注意接单前最好自己再改一遍代码逻辑,以免撞衫)。
这套代码我后续还会继续更新,目前已经在计划内的版本迭代方向包括:
- 优惠券模块:后端做好优惠券发放、核销接口,前端在结算页接入优惠券选择器;双十一这种大促场景,没有优惠券的商城根本没法玩。
- 秒杀活动:利用 Redis 做库存预扣,解决高并发下的超卖问题。这个话题展开讲又是一篇长文,等我测试稳定了再单独发一篇文章聊。
- 后台管理界面:目前后台只有 API,我会再写一个简单易用的后台模板,直接套一个开源 admin 框架,让商品管理不用再敲命令行。
- 多商户支持:现在的手办圈子里很多人是二道贩子,如果能做成平台模式让多个商家各自上架商品,商业价值会大很多。
我自己在实际操作中最大的体会是:一套商城系统的难点根本不在“写代码”,而在于把用户流程、库存、订单状态这些业务逻辑厘清楚。框架只是工具,真正的活全在业务设计里。这套手办小程序源码我希望帮大家跳过“业务设计”这个坑,直接拿到一套完整可用的方案,在你自己的场景里去润色、去扩展。有问题欢迎在评论区交流,我看到都会回。