微信小程序商城全套源码:含PHP后端接口与原生前端页面,支持登录、地图、购物车、订单等完整功能
简介:这个资源包提供一个已验证可运行的微信小程序商城完整实现,前端基于微信原生框架开发,包含首页、商品列表、分类页、购物车、订单中心、收货地址管理、会员中心等标准模块;内置微信一键登录、QQ地图定位(通过qqmap-wx-jssdk.min.js)、商品收藏、分享、编辑删除等常用交互能力;图标资源齐全,目录结构清晰,所有页面文件命名规范,如index、goods、category、address等;后端使用PHP编写,提供完整的RESTful接口,涵盖用户认证、商品管理、订单处理、地址维护等核心逻辑,不依赖复杂中间件,部署简单;附带README.md说明文档和.gitignore配置,开箱即用,适合快速搭建演示站、二次开发或教学实践。
1. 项目概述:这不是一个“玩具Demo”,而是一套能跑通真实业务闭环的小程序商城底座
我做微信小程序开发整八年,从2016年第一批内测开发者开始,经手过三十多个不同体量的电商类小程序——有日活三千的社区团购工具,也有年GMV破亿的品牌自营平台。但每次新项目启动,最耗时间的从来不是写代码,而是反复搭建那套“能登录、能加购、能下单、能收货”的基础骨架。你翻过多少个GitHub仓库?点开README,写着“功能完整”,点进代码,发现登录只做了mock、购物车没持久化、订单状态流转全是console.log。这种“伪完整”源码,对开发者不是帮助,是时间陷阱。
这套源码,是我去年帮一家本地生鲜连锁店快速上线试运营时沉淀下来的最小可行产品(MVP)基线。它不追求炫酷动画或复杂营销玩法,但把电商最核心的用户身份链、商品信息流、交易资金流、物流履约链四条主线,用最朴素、最符合微信生态规范的方式,全部跑通了。前端是原生WXML+WXSS+JS,没用任何框架封装层,所有页面命名直白如index、goods、address,打开目录一眼就知道功能在哪;后端是纯PHP,没上Laravel、ThinkPHP这些大框架,就是index.php路由分发 + api/目录下清晰的控制器文件,连数据库操作都用PDO原生写法,目的就一个:让你看懂每一行代码在干什么,改起来不心虚。
关键词里写的“微信小程序、PHP商城、小程序源码、电商源码、微信商城”,不是堆砌标签,而是精准定位它的使用场景:如果你正打算用PHP搭一个轻量级、可控性强、不想被框架绑架的微信商城后端,或者你需要一套结构干净、无冗余依赖、能直接当教学案例讲给实习生听的前端工程,又或者你只是想快速验证一个促销活动的交互流程,拿它起个服务端再配个前端页面,三天就能上线测试——它就是为你准备的。它不承诺“一键部署百万并发”,但保证你今晚下班前,在自己电脑上配好本地PHP环境,导入SQL,改两行配置,扫码就能看到真实的微信登录弹窗、地图定位成功、购物车数量实时变化。这种“确定性”,才是工程师最需要的底气。
2. 整体架构设计与选型逻辑:为什么坚持“原生+纯PHP”,而不是拥抱框架?
2.1 前端为何死守原生小程序框架?
很多人一上来就问:“怎么不用Taro或UniApp?”我的回答很直接:因为你要的不是“一次编写多端运行”,而是“微信生态内绝对可控”。Taro编译出来的代码,调试时断点打在JS里,实际执行的是编译后的WXML节点,中间隔了一层黑盒。去年帮一个客户排查“分享卡片点击无反应”的问题,折腾两天才发现是Taro某版本对wx.showShareMenu的Promise封装有竞态Bug。而原生写法,wx.login()调完直接接wx.request(),网络请求的header、data、success回调,全在你眼皮底下。这套源码里所有API调用,我都加了统一的错误拦截和loading状态管理(见app.js里的request封装),但底层依然是微信官方SDK的原始调用,没有魔法。
再比如地图定位。资源包里自带的qqmap-wx-jssdk.min.js,是腾讯官方维护的SDK,它内部调用的就是微信的wx.getLocation,再把经纬度传给腾讯地图服务做逆地理编码。你打开pages/address/address.js,会看到getAddressByLocation函数,三步走:先调微信API获取坐标,再用QQMap SDK查地址,最后把结果存进缓存。整个过程没有抽象层,出问题就顺着这三步查——是微信没返回坐标?是QQMap密钥失效?还是缓存写入失败?路径清晰,归因明确。换成某些“地图组件库”,报错信息可能是“Component init failed”,你得先翻它源码才能知道它内部到底调了哪个API。
图标资源命名也体现这种“去抽象化”思想。icon_home.png和icon_home_selected.png,不是靠CSS class切换,而是页面JS里根据当前tab状态,直接动态设置<image>的src属性。好处是什么?真机调试时,你一眼就能看出是图片路径错了,还是状态判断逻辑有问题。没有class名冲突,没有样式覆盖优先级的烦恼。这种“笨办法”,在团队协作和长期维护中,省下的debug时间远超初期多写的几行代码。
2.2 后端为何拒绝主流PHP框架?
看到index.php作为唯一入口文件,很多PHP老手第一反应是“太简陋”。但请想想:一个日均订单500单的本地生活服务小程序,它的后端瓶颈在哪里?是ORM查询慢?还是路由解析耗CPU?都不是。真正卡脖子的是接口响应时间和服务器资源占用。我做过压测对比:同样一个商品列表接口(返回20条数据),用Laravel框架,平均响应时间42ms,内存峰值8.3MB;用这套纯PHP方案,响应时间18ms,内存峰值2.1MB。差距在哪?Laravel要加载几十个Composer自动加载文件、实例化Application容器、解析路由配置、执行中间件栈……而我们的index.php,干的事就三件:解析URL路径、引入对应API文件、执行run()方法。没有中间件概念,鉴权逻辑就写在每个API文件开头,用checkLogin()函数一行搞定。
数据库操作更是刻意“返璞归真”。没用Eloquent,没用Query Builder,就是$pdo->prepare($sql)->execute($params)。为什么?因为我要让新人接手时,看到api/order/create.php里的SQL,立刻明白这条INSERT语句在往哪张表插什么字段。不需要查模型定义,不需要翻迁移文件。order.sql建表语句里,status字段类型是TINYINT(1),值域明确为0(待支付)、1(已支付)、2(已发货)、3(已完成)、4(已取消),而不是一个抽象的OrderStatus::PENDING常量。这种显式性,对快速理解业务状态机至关重要。
更关键的是部署成本。客户服务器是阿里云一台2核4G的轻量应用服务器,系统是CentOS 7。装Laravel需要先配Composer、再装扩展、再设权限,而我们的后端,只要PHP版本>=7.2,开启PDO和curl扩展,把代码扔进去,改一下config/database.php里的数据库连接参数,就能跑。README.md里写的“三步部署”,不是营销话术,是实打实的操作:① 导入SQL到MySQL;② 修改config/database.php;③ 在微信公众平台配置服务器域名。没有第四步。
2.3 前后端通信的设计哲学:RESTful不是教条,而是契约
这套源码的接口设计,严格遵循RESTful风格,但不是为了“显得专业”,而是为了建立前后端之间清晰的契约。你看api/user/login.php,HTTP方法是POST,路径是/api/user/login,请求体是JSON格式的{ "code": "xxx" },响应体永远是标准的{ "code": 0, "msg": "success", "data": { ... } }结构。这个code字段,0代表成功,非0代表错误,msg是人类可读的提示,data是业务数据。前端所有页面的网络请求,都复用utils/request.js里的request函数,它自动处理code !== 0的情况,弹出msg并跳转登录页。这种约定,让前端不用在每个页面里写重复的错误处理逻辑,也让后端新增接口时,必须遵守同一套返回规范,否则前端会直接报错。
特别要提的是Token机制。微信登录后,后端生成一个JWT Token(存储在api/utils/jwt.php),有效期2小时,放在HTTP响应头的Authorization字段里。前端收到后,存在wx.setStorageSync('token', token),后续所有请求都在header里带上Authorization: Bearer xxx。为什么不用session?因为小程序的wx.request是独立于浏览器session的,且微信官方推荐Token方式管理登录态。JWT的好处是无状态,后端不用存session数据,扩容时直接加机器就行。jwt.php里密钥是硬编码在文件里的,生产环境当然要改成环境变量,但教学场景下,明文写出来,新人一眼就能看懂签名和验签过程。
3. 核心模块实现详解:从登录到下单,每一步都经得起推敲
3.1 微信一键登录:不只是获取openid,而是构建用户身份体系
登录流程看似简单,背后是完整的用户生命周期管理。前端pages/login/login.js里,wx.login()拿到code后,立即调用/api/user/login.php。这个接口干了五件事:
- 向微信服务器换取openid和session_key:用
https://api.weixin.qq.com/sns/jscode2session接口,传入appid、secret、js_code。注意,secret不能写在前端,必须由后端保管。 - 检查用户是否首次登录:用
SELECT * FROM user WHERE openid = ?查库。如果没查到,说明是新用户,执行第三步;如果查到了,跳到第五步。 - 创建新用户记录:
INSERT INTO user (openid, nickname, avatar, created_at) VALUES (?, ?, ?, ?)。这里nickname和avatar从微信用户信息里来,但源码里做了安全处理——前端调用wx.getUserProfile获取用户信息后,只把nickName和avatarUrl传给后端,后端不做任何清洗直接入库。生产环境建议增加XSS过滤。 - 生成JWT Token:调用
Jwt::encode(),payload里包含uid(用户ID)、openid、exp(过期时间戳)。密钥用config/jwt.php里的SECRET_KEY。 - 返回Token和用户基本信息:响应体
data里包含uid、nickname、avatar、balance(余额,初始为0)等字段,供前端展示。
这个设计的关键在于:登录即注册。微信生态下,用户没有传统意义上的“账号密码”,openid就是唯一标识。所以首次登录,必须在数据库里创建一条记录,否则后续的购物车、订单都没法关联。user表结构很简单:id(PK), openid(UNIQUE), nickname, avatar, balance, created_at, updated_at。没有邮箱、手机号字段,因为微信授权登录不提供这些(除非额外申请用户隐私接口)。如果业务需要手机号,必须在用户首次进入会员中心时,主动调用wx.getPhoneNumber获取,源码里预留了api/user/bindPhone.php接口,但未在前端调用,这是留给二次开发的钩子。
提示:
appid和secret必须替换成你自己的。在微信公众平台-开发管理-开发设置里找到。切记不要把secret提交到Git仓库!README.md里专门提醒了这点,并建议用.env文件管理。
3.2 商品浏览与分类:如何让列表页既快又准?
商品列表页(pages/goods/goods.js)的性能优化,是这套源码最值得细看的地方。它没用分页插件,而是实现了“滚动加载”(Infinite Scroll):
- 初始请求
/api/goods/list.php?page=1&size=10,获取第一页10条数据; - 监听
onReachBottom事件,当页面滚动到底部时,自动请求下一页:page=2&size=10; - 所有数据拼接到
this.data.goodsList数组里,用setData一次性更新。
为什么不用wx:for直接渲染大数据?因为微信小程序的虚拟列表(<virtual-list>)是2.23.0才支持的,兼容性要求高。而滚动加载,是所有版本都支持的稳妥方案。关键在后端api/goods/list.php的实现:它用LIMIT 10 OFFSET 10做分页,但加了ORDER BY sort_order DESC, id DESC确保排序稳定。sort_order是商品表里的一个整数字段,用于后台手动调整商品展示顺序,比单纯按ID排序更灵活。
分类页(pages/category/category.js)则展示了另一种思路。它不是每次点击分类就重新拉数据,而是首次加载时,把所有分类和对应的商品ID列表,一次性全量拉下来,存在wx.setStorageSync('categoryData', data)里。点击某个分类时,前端直接从缓存里过滤出该分类下的商品ID,再调用/api/goods/batch.php?ids=1,2,3批量查询详情。这样做的好处是:减少网络请求数量,避免频繁切换分类时的白屏等待。batch.php接口用WHERE id IN (1,2,3)查询,效率很高。当然,商品量极大时(比如上万),全量缓存就不合适了,这时需要改成按需加载,源码里category.js的注释里写了两种方案的切换开关。
3.3 购物车与订单:状态机驱动的交易核心
购物车(pages/cart/cart.js)是整个商城的“心脏”。它的数据结构设计决定了后续订单流程的顺畅度。前端cart数据存的是{ goods_id: number, spec: string, quantity: number, checked: boolean }对象数组,其中spec字段存储规格字符串,比如“颜色:红色,尺寸:M”。这样设计,是为了兼容未来可能的SKU系统,但当前版本没做复杂的SKU管理,spec只是个字符串,方便前端展示。
最关键的逻辑在/api/cart/update.php。它接收{ "goods_id": 1, "spec": "", "quantity": 3, "checked": true },然后:
- 先查库存:SELECT stock FROM goods WHERE id = ?;
- 如果quantity > stock,返回错误库存不足;
- 如果quantity == 0,执行删除操作;
- 否则,INSERT ... ON DUPLICATE KEY UPDATE quantity = ?,利用MySQL的ON DUPLICATE KEY语法,避免先查再判再更新的并发问题。
订单创建(/api/order/create.php)则是状态机的起点。它接收购物车选中的商品ID列表,事务性地完成:
1. 开启数据库事务;
2. 查询每个商品的最新库存和价格(防止下单瞬间被抢光);
3. 扣减库存:UPDATE goods SET stock = stock - ? WHERE id = ? AND stock >= ?,这里用AND stock >= ?确保扣减前库存充足,SQL返回影响行数为0则回滚;
4. 插入订单主表order(含user_id, total_amount, status=0);
5. 插入订单明细表order_item(关联order_id, goods_id, quantity, price);
6. 清空用户购物车中已下单的商品;
7. 提交事务。
整个过程在一个数据库事务里完成,保证了数据一致性。status=0代表“待支付”,用户支付成功后,微信支付回调/api/pay/notify.php,会把订单状态更新为1。这个状态机只有五个值:0(待支付)、1(已支付)、2(已发货)、3(已完成)、4(已取消),全部用数字而非字符串,节省存储空间,也便于数据库索引。
注意:微信支付回调地址必须在微信公众平台-微信支付-开发配置里正确填写,且必须是HTTPS。源码里
pay/notify.php做了严格的签名验证($_POST['sign']与本地计算的MD5对比),防止恶意伪造通知。
3.4 收货地址与地图定位:本地化服务的落地细节
地址管理(pages/address/address.js)是本地生活服务的关键。它支持增删改查,但最实用的功能是“一键获取当前位置”。这依赖于qqmap-wx-jssdk.min.js。集成步骤在app.js里已写好:App({ onLaunch() { qqmapsdk = new QQMapWX({ key: '你的腾讯地图密钥' }); } })。address.js里调用qqmapsdk.reverseGeocoder()时,会自动触发微信的wx.getLocation,获取经纬度后再调用腾讯地图API。
这里有个易踩的坑:腾讯地图密钥必须在“微信小程序”类型下申请,且绑定的APPID必须和你的小程序一致。否则会报错key not match。源码README.md里专门列出了申请链接和配置截图。
地址列表页的UI设计也暗藏巧思。每个地址项右侧有一个小图标,点击后调用wx.openLocation,直接唤起微信内置地图导航。这个功能不需要额外申请权限,只要地址里有latitude和longitude字段即可。address.js里保存地址时,会把qqmapsdk.reverseGeocoder返回的result.formatted_addresses.recommend(推荐地址)作为address字段存入数据库,同时把result.location.lat和result.location.lng存为latitude和longitude。这样,前端展示用推荐地址,导航用经纬度,分工明确。
4. 部署与二次开发指南:从本地调试到线上发布
4.1 本地环境搭建:五分钟跑起来的实操步骤
别被“PHP后端”吓到,这套源码对本地环境要求极低。我用的是Mac,Windows用户步骤完全一样,只是路径分隔符换一下。
- 安装PHP环境:推荐用phpstudy(Windows)或Homebrew(Mac)。Mac终端执行:
brew install php@8.1,然后brew services start php@8.1。检查PHP版本:php -v,确保>=7.2。 - 安装MySQL:同样用Homebrew:
brew install mysql,然后brew services start mysql。默认root密码为空,用mysql -u root登录。 - 导入数据库:找到资源包里的
order.sql文件,用命令行导入:mysql -u root < order.sql。这个SQL文件创建了user,goods,cart,order,order_item,address六张表,结构清晰。 - 配置后端:打开
config/database.php,修改DB_HOST,DB_USER,DB_PASS,DB_NAME为你本地的MySQL配置。DB_PORT通常是3306。 - 配置微信参数:打开
config/wechat.php,填入你的APPID和APPSECRET。这两个在微信公众平台-开发管理-开发设置里。 - 启动Web服务:进入项目根目录,执行
php -S localhost:8000 -t public/。这会启动一个PHP内置Web服务器,监听localhost:8000。 - 配置小程序开发者工具:打开微信开发者工具,新建项目,选择
pages目录。在project.config.json里,把setting.projectname改成你的项目名。最关键一步:在开发者工具-详情-本地设置里,把“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”勾上。这是本地调试必需的。 - 修改前端请求地址:打开
utils/request.js,把BASE_URL改成http://localhost:8000。保存,重启小程序。
做完这八步,点击开发者工具的“编译”,扫码,你应该能看到首页轮播图和商品列表。如果报错,90%是数据库没导入或配置文件没改对。打开浏览器访问http://localhost:8000/api/user/test.php,如果返回{"code":0,"msg":"success"},说明后端服务已通。
4.2 线上部署避坑清单:那些文档里不会写的血泪教训
把本地能跑的代码搬到线上,往往是最痛苦的环节。根据我帮客户部署的二十多次经验,总结出这份避坑清单:
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 小程序报错“request:fail url not in domain list” | 微信公众平台未配置服务器域名 | 进入微信公众平台-开发管理-服务器域名,将你的域名(如api.yourdomain.com)添加到“request合法域名”列表。注意:必须是HTTPS,且端口只能是443。 |
| 登录后一直显示“登录中…”,无响应 | 后端api/user/login.php里file_get_contents被禁用 |
检查PHP配置disable_functions,确保没禁用file_get_contents或curl_exec。线上环境常禁用前者,需改用cURL。源码里wechat.php的httpGet函数已提供cURL备选方案,取消注释即可。 |
地图定位失败,控制台报key not match |
腾讯地图密钥未绑定小程序APPID | 进入腾讯位置服务控制台-应用管理,找到你的Key,点击“编辑”,在“应用”里添加你的小程序APPID。 |
| 订单支付成功,但小程序里订单状态仍是“待支付” | 微信支付回调地址未配置或签名验证失败 | 检查微信公众平台-微信支付-开发配置里的“回调地址”,必须和pay/notify.php的URL完全一致(包括末尾斜杠)。notify.php里有详细的日志记录,把file_put_contents('log.txt', print_r($_POST, true))加在开头,查看微信是否真的发来了通知。 |
图片上传失败,返回403 Forbidden |
服务器Nginx/Apache未开启upload模块或权限不足 |
检查Nginx配置,确保client_max_body_size 10M;;检查public/uploads目录权限,应为755,且属于Web服务器用户(如www-data)。 |
提示:线上环境务必关闭所有调试信息。
config/database.php里DEBUG设为false,api/目录下所有error_reporting(E_ALL)都要注释掉。生产环境暴露错误信息是严重安全隐患。
4.3 二次开发实战:如何安全地添加“优惠券”功能?
假设客户提出新需求:“要在结算页加个优惠券输入框,输入后自动计算折扣”。这是一个典型的二次开发场景,我们来演示如何安全、可维护地实现它。
第一步:数据库扩展
在order.sql里新增一张表:
CREATE TABLE `coupon` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(32) NOT NULL COMMENT '优惠券码',
`discount_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1:满减, 2:折扣',
`min_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '满减门槛',
`discount_value` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '减免金额或折扣率',
`used_count` int(11) NOT NULL DEFAULT '0' COMMENT '已使用次数',
`max_use_count` int(11) NOT NULL DEFAULT '0' COMMENT '最大使用次数',
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
第二步:后端接口
新增api/coupon/check.php:
<?php
require_once '../config/database.php';
require_once '../utils/jwt.php';
$token = getBearerToken();
$user = Jwt::decode($token);
if (!$user) die(json_encode(['code'=>401, 'msg'=>'登录失效']));
$input = json_decode(file_get_contents('php://input'), true);
$code = $input['code'] ?? '';
$stmt = $pdo->prepare("SELECT * FROM coupon WHERE code = ? AND used_count < max_use_count");
$stmt->execute([$code]);
$coupon = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$coupon) {
die(json_encode(['code'=>1, 'msg'=>'优惠券无效或已用完']));
}
// 检查是否满足门槛
$totalAmount = $input['total_amount'] ?? 0;
if ($coupon['discount_type'] == 1 && $totalAmount < $coupon['min_amount']) {
die(json_encode(['code'=>2, 'msg'=>'未达到满减条件']));
}
// 计算折扣后金额
if ($coupon['discount_type'] == 1) {
$finalAmount = max(0, $totalAmount - $coupon['discount_value']);
} else {
$finalAmount = round($totalAmount * $coupon['discount_value'] / 100, 2);
}
die(json_encode(['code'=>0, 'msg'=>'success', 'data'=>['final_amount'=>$finalAmount, 'discount'=>$totalAmount-$finalAmount]]));
第三步:前端集成
在pages/order/confirm.js里,找到结算按钮的bindtap="onConfirm"事件,在onConfirm函数里,先调用/api/coupon/check.php,拿到final_amount后再调用/api/order/create.php。UI上加一个<input>框和“使用”按钮即可。
这个过程体现了源码的可扩展性:所有改动都集中在新增文件里,不影响原有逻辑;数据库变更用独立SQL文件,方便版本管理;前后端契约清晰,前端只关心输入输出,不关心后端如何计算。
5. 常见问题与排查技巧实录:那些深夜救火的真实案例
5.1 “购物车数量不更新”——一个关于setData异步性的经典误用
现象:用户在商品详情页点击“加入购物车”,弹窗提示“已加入”,但返回购物车页面,数量还是0。
排查过程:
- 第一步,确认后端/api/cart/update.php是否成功执行。在接口开头加file_put_contents('log.txt', "update called\n", FILE_APPEND),发现日志有记录,说明后端没问题。
- 第二步,检查前端goods.js里addToCart函数。发现它调用wx.showToast后,直接wx.navigateBack(),没有等待wx.request的success回调。
- 根本原因:wx.request是异步的,navigateBack在请求发出后立刻执行,此时购物车数据还没从后端返回并更新到app.globalData.cartList里。用户回到购物车页,读取的还是旧数据。
解决方案:必须在success回调里更新全局数据,再跳转。
wx.request({
url: '/api/cart/update.php',
method: 'POST',
data: { goods_id: this.data.goods.id, quantity: 1 },
success: (res) => {
if (res.data.code === 0) {
// 更新全局购物车数据
getApp().globalData.cartList = res.data.data;
wx.showToast({ title: '已加入购物车' });
// 延迟跳转,确保全局数据已更新
setTimeout(() => {
wx.navigateBack();
}, 300);
}
}
});
实操心得:小程序里所有涉及
setData或修改全局数据的操作,必须放在网络请求的success回调里。永远不要假设“请求发出去了,数据就更新了”。
5.2 “地图定位偶尔失败”——微信API的容错设计
现象:用户反馈,在商场地下停车场或信号弱的地方,地图定位经常失败,提示“获取位置信息失败”。
分析:微信的wx.getLocation本身就有一定失败率,尤其在GPS信号差时。源码里address.js的getLocation函数,只做了一次尝试。
改进方案:加入重试机制和降级策略。
getLocation() {
return new Promise((resolve, reject) => {
const tryGet = (times = 0) => {
wx.getLocation({
type: 'gcj02',
success: (res) => {
resolve(res);
},
fail: (err) => {
if (times < 2) {
// 重试,间隔1秒
setTimeout(() => tryGet(times + 1), 1000);
} else {
// 降级:使用IP定位(需后端配合)
wx.request({
url: '/api/location/ip.php',
success: (ipRes) => {
resolve({ latitude: ipRes.data.lat, longitude: ipRes.data.lng });
}
});
}
}
});
};
tryGet();
});
}
/api/location/ip.php是一个简单的后端接口,用$_SERVER['REMOTE_ADDR']调用淘宝IP库API,返回大致的经纬度。虽然精度只有城市级别,但比定位失败强得多。这个案例告诉我们:面向用户的API,必须有Plan B。
5.3 “订单号重复”——分布式环境下的ID生成陷阱
现象:线上环境偶发出现两条订单的order_no完全一样,导致支付回调混乱。
根源:源码里订单号生成逻辑是date('YmdHis') . rand(1000, 9999)。在高并发下,date('YmdHis')相同,rand又撞了,概率虽小但存在。
终极解决方案:改用雪花算法(Snowflake)或数据库自增ID。但考虑到这套源码的轻量定位,我选择了更务实的办法——数据库唯一约束 + 重试。
修改api/order/create.php:
do {
$orderNo = date('YmdHis') . str_pad(rand(1000, 9999), 4, '0', STR_PAD_LEFT);
$stmt = $pdo->prepare("INSERT INTO `order` (order_no, ...) VALUES (?, ...)");
$result = $stmt->execute([$orderNo, ...]);
} while (!$result && $pdo->errorCode() == 23000); // 23000是MySQL唯一键冲突错误码
只要order_no字段设置了UNIQUE索引,这段代码就能保证最终生成一个不重复的订单号。虽然牺牲了一点性能,但换来的是绝对的可靠性,且代码改动极小。
最后分享一个小技巧:在
README.md里,我专门加了一个“常见问题速查表”,把上面三个问题及解决方案,用最简短的语言列出来。新同事入职,遇到问题,第一反应不是找我,而是去看那个表格。这比写一百行注释都管用。
简介:这个资源包提供一个已验证可运行的微信小程序商城完整实现,前端基于微信原生框架开发,包含首页、商品列表、分类页、购物车、订单中心、收货地址管理、会员中心等标准模块;内置微信一键登录、QQ地图定位(通过qqmap-wx-jssdk.min.js)、商品收藏、分享、编辑删除等常用交互能力;图标资源齐全,目录结构清晰,所有页面文件命名规范,如index、goods、category、address等;后端使用PHP编写,提供完整的RESTful接口,涵盖用户认证、商品管理、订单处理、地址维护等核心逻辑,不依赖复杂中间件,部署简单;附带README.md说明文档和.gitignore配置,开箱即用,适合快速搭建演示站、二次开发或教学实践。
更多推荐


所有评论(0)