做过不少电商类的项目,但基于微信小程序的精致护肤购物系统这套组合下来,确实有它值得单独拎出来聊一聊的地方。整个工程涉及uniapp小程序端、Vue管理后台、PHP和Node.js双后端服务,光看技术栈就知道这不是一个玩具项目,而是那种真正要在微信生态里跑业务、扛流量的正经商城系统。
我最早接触这类需求的时候,第一反应也是“商城而已,套个模板改改就行”。但真正深入之后发现,护肤美妆品类有它非常特殊的商品结构、内容运营逻辑和用户决策路径,再加上微信小程序环境的限制、支付流程的合规要求,整个系统的设计难度并不低。所以我打算从项目整体拆解开始,把技术选型的理由、数据建模的思路、核心功能的实现细节以及部署打包过程中那些坑,完整地过一遍。
1. 项目整体设计与技术选型拆解
1.1 为什么是PHP + Node.js 双后端而不是一套框架走到底
很多同学看到“PHP_nodejs”这种组合,第一反应是“是不是为了凑简历技术栈”。但其实在真实的商城系统里,双后端是非常常见的架构决策,核心思路是:让每个服务只做自己最擅长的事。
- PHP侧负责管理后端的业务接口,比如商品管理、分类管理、订单管理、用户管理、营销活动配置。选择PHP并不是因为它性能有多极致,而是因为这个团队如果长期做电商类项目,PHP在快速迭代业务逻辑、对接各种CMS和后台管理框架时效率极高,Laravel和ThinkPHP的生态里现成的商城模块、权限管理、Excel导入导出都是开箱即用。
- Node.js侧负责小程序端的C端接口、微信登录、手机号授权、微信支付回调这些偏并发、偏实时、偏微信生态对接的逻辑。Node.js的非阻塞I/O在处理支付回调、订单状态推送这类高频率轻量级请求时,性能表现更稳定,而且npm生态里
wechat-oauth、wechat-pay之类的库很成熟,省去不少造轮子的时间。
双后端之间通过HTTP接口或者消息队列通信,小程序端只认Node.js这一层网关,PHP的管理接口不直接暴露到公网,降低被刷被攻击的风险。这套架构还有一个很实际的好处:两个服务可以独立部署、独立扩容,双十一或者大促的时候,可以只给Node.js侧多开几个实例,而PHP后台不用跟着受罪。
1.2 前端选uniapp而不是原生小程序的原因
说实话,纯从微信小程序性能上讲,原生写法永远是上限最高的。但接这类商业项目,需要在效率和性能之间做出取舍,这个时候uniapp的优势是绕不开的。
首先,uniapp可以直接编译到微信小程序、H5、App等多个平台。护肤类品牌方经常会有“先做小程序,然后要出抖音小程序、还要做App”的需求,一套Vue代码多端复用,后端的接口只用维护一份,成本优势非常明显。其次,团队的技术栈如果是Vue背景,招人、交接、维护都比重新学小程序原生WXML语法友好得多。
当然,uniapp也不是没有代价。比如在小程序里要使用一些微信特有的能力(像wx.getUserProfile、wx.login、getPhoneNumber这些),uniapp的API封装了一层uni.login、uni.getUserProfile,偶尔会出现API参数和小程序原生对不上的情况。这时候需要在小程序开发工具里打开“不校验合法域名”或者用条件编译去调原生API,这些细节后面在实操部分我会展开。
1.3 护肤商城和普通商城到底差在哪里
回到业务层面。如果你只是卖标品3C或者服装,商品详情页放几张图、几个参数就能撑起来。但护肤化妆品这种品类,用户决策特别依赖内容,产品的成分、功效、肤质匹配度、使用步骤甚至视频教程,都是影响转化率的关键。
这意味着商城系统不能只做“商品表+SKU表+购物车”这套标准电商模型,还需要在商品详情的数据结构里预留:
- 成分表(支持前端风险成分提示)
- 肤质标签(干皮、油皮、敏感肌)
- 功效标签(保湿、美白、抗老、修护)
- 视频教程、图文种草内容位
另外护肤品的规格也很有意思,有15ml中样、30ml正装、套盒组合,价格、库存、佣金比例全都是分开的。如果数据模型在初期设计时没有把“SKU与规格”这个维度铺开,后面接活动、接分销都会很痛苦。我见过不少项目因为最初图省事,把规格直接写死在商品表里,导致后面做满减活动时,每个规格都变成一个商品来处理,维护成本直接爆炸。
2. 护肤化妆品商城的核心数据建模
2.1 商品模型:SPU/SKU与护肤规格
商品模型是整个商城的地基。我通常按SPU/SKU的标准电商模型来做,但在SKU层针对护肤品类额外扩展字段。
- SPU表(
product):一个商品的概念级定义,比如“XX品牌玻尿酸精华液”。字段至少包括:商品名称、副标题、品牌ID、类目ID、主图、详情图文、视频URL、运费模板ID、上下架状态。 - SKU表(
product_sku):具体可售的规格单位,比如“30ml装”、“50ml装”、“买一送一套盒”。每个SKU有独立的条形码、价格、市场价、成本价、库存、重量、体积。 - 规格维度表(
sku_spec):这里是护肤品的特色。除了常规的“型号/颜色”之外,还要支持“ml数/瓶身规格/是否赠品”等自定义规格组。
关于价格的字段设计,有一个非常关键的细节:永远至少保留三个价格字段——售价、市场价、成本价。售价用于前端展示和成交;市场价用于划线展示,拉高折扣感知;成本价用于后台毛利统计,绝不允许通过接口暴露给小程序端。如果后续要做秒杀、拼团之类的活动,再加一个活动价字段,而不是去覆盖原售价。
2.2 护肤分类与标签体系
护肤品的分类普遍是树状的,层级比较深,例如:
美妆护肤 ├── 护肤 │ ├── 洁面 │ ├── 化妆水 │ ├── 精华 │ ├── 乳液/面霜 │ └── 面膜 ├── 彩妆 │ ├── 底妆 │ ├── 口红 │ └── 眼妆 └── 身体护理 ├── 身体乳 └── 手部护理分类表用parent_id自关联,最多支持三层。分类表里要放icon、banner、sort_order、is_show,因为小程序首页通常需要按分类维度做专题楼层。
比分类更重要的是标签体系。护肤商城一定要做“肤质”和“功效”这两种标签维度,它们对应小程序端的筛选器。在小程序商品列表页顶部提供“肤质:干皮/油皮/混油/敏感肌”和“功效:补水/美白/抗老/修护”的筛选项。实现方式不建议做成复杂的多对多关系后用SQL联表查询,而是在商品表里直接保存一个JSON字段,比如skin_type_tags: ["干皮", "敏感肌"],查询时用JSON_CONTAINS处理,简单高效,维护成本低。
2.3 用户、会员和积分设计
商城系统的用户体系不只是存一个openid那么简单。微信小程序端用户表至少需要:
openid:微信用户唯一标识unionid:如果品牌方同时有公众号、小程序、App,unionid用来打通多端身份nickname、avatar:用户资料phone:通过手机号授权换取grade:会员等级balance:余额points:积分
会员等级在护肤品类里特别重要,品牌方往往有“普通会员、银卡会员、金卡会员、黑卡会员”的梯度,不同等级对应不同的折扣率。等级判断既可以通过累计消费金额自动升级,也可以由后台人工调整。积分这块我建议做两层:一是消费送积分,二是签到送积分。签到功能虽然看起来小,但确实是护肤类小程序提升日活最有效的功能之一。
3. 微信小程序端核心功能实现
3.1 uniapp创建项目与目录结构
在HBuilderX里新建项目时选“uni-app”模板,然后选择“默认模板”。注意不要选Hello uni-app那种带很多演示页的模板,后面清起来很麻烦。项目结构按功能模块拆分,不要所有页面都堆在pages根目录下:
src/ ├── pages/ │ ├── index/ // 首页 │ ├── category/ // 分类页 │ ├── cart/ // 购物车 │ ├── user/ // 个人中心 │ ├── goods/ // 商品详情 │ ├── order/ // 订单列表/确认订单 │ └── search/ // 搜索 ├── api/ // 接口统一封装 ├── components/ // 公共组件 ├── store/ // Pinia/Vuex ├── utils/ // 工具函数 └── static/ // 静态资源工程创建完之后,必须在manifest.json里改三个东西:微信小程序AppID、小程序名称、接口请求合法域名。前两个不会配的话连预览都跑不起来,域名如果不在微信后台配置好,真机一请求接口就报request:fail url not in domain list。
3.2 微信登录与手机号授权(重要)
微信小程序的登录和App端登录有一个很本质的区别:小程序端不建议自己在客户端做账密登录,而是走微信OpenID体系。流程是:
- 客户端调用
uni.login(),拿到临时code。 - 把
code传给后端Node.js接口。 - Node.js拿着
code去调用微信的jscode2session接口,换取openid和session_key。 - 后端用
openid查用户,不存在则自动注册;最后返回自定义登录态token。
这个token后续通过请求头Authorization: Bearer <token>传递,服务端做鉴权。
手机号授权是一个单独的流程,微信规定用户主动点击按钮触发getPhoneNumber事件才能获取:
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">授权手机号</button>async getPhoneNumber(e) { if (e.detail.errMsg !== 'getPhoneNumber:ok') { return; // 用户拒绝了 } // 把加密数据发给后端,由后端解密 const res = await this.$api.bindPhone({ code: e.detail.code, encryptedData: e.detail.encryptedData, iv: e.detail.iv }); }注意,2023年之后微信调整了规则,通过getPhoneNumber拿到的动态code是主流方式,后端用code直接调微信接口换手机号,不再建议用encryptedData解密那一套老方案。微信的规则经常变,这块一定要以最新官方文档为准。
3.3 商品列表、搜索、购物车与商品详情
商品列表页通常有三种触发入口:首页分类楼层点击、分类Tab进入、搜索页关键词搜索。我在实际项目中统一用同一个组件ProductCard来渲染商品卡片,卡片里展示商品图、标题、价格、已售数量、“敏感肌适用”之类的角标。列表页用onReachBottom触发下一页加载,page和size分页参数向后端传,每次返回的数据里带hasMore布尔值,避免“加载更more”按钮的判断出错。
商品详情页是小程序端交互最重的页面,核心模块包括:
- 轮播图(支持视频首帧/多图)
- 价格、销量、划线价展示
- 规格选择弹窗(选中规格后切换SKU和价格)
- 详情图文懒加载
- “加入购物车”和“立即购买”按钮
- 客服按钮(跳转微信客服或自建客服)
SKU切换的算法值得多写一句:前端拿到的是“规格组合与SKU的映射表”,例如{"30ml/清爽型": {"skuId":123, "price": 299, "stock": 50}}。用户每点一个规格,前端就做一次笛卡尔积匹配,找到当前选中的规格组合对应的SKU和价格并刷新。这个逻辑如果前端不处理好,就会出现“价格不变但库存错误”的严重事故。
购物车模块我倾向用本地缓存uni.setStorageSync保存一份,同时在后端也存一份,关键接口以服务端数据为准。因为护肤品经常有“买2件享7折”之类营销规则,购物车结算时的优惠计算必须由后端算,前端只负责展示结果。
3.4 微信支付与订单流程
支付是商城系统里最不能出错的部分。小程序端调用支付非常简单:
const orderData = await this.$api.createOrder({ skuId, count, addressId }); const payParams = await this.$api.wxPay({ orderNo: orderData.orderNo }); uni.requestPayment({ provider: 'wxpay', timeStamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, signType: 'RSA', paySign: payParams.paySign, success: (res) => { // 跳转到订单列表/支付成功页 }, fail: (err) => { // 提示用户支付失败 } });关键点在于:客户端拿到的payParams必须由Node.js后端向微信统一下单接口请求获得,同时后端需要用同样的参数生成签名。这块最容易踩的坑是签名算法版本不一致。微信支付现在推荐RSA签名,但很多老项目还是HMAC-SHA256,小程序端如果按固定的signType去验签,一旦后端用了不同的密钥类型就会直接失败。
另外,支付回调是重中之重。Node.js收到微信支付回调之后,必须做这几件事,顺序不能乱:
- 验签,确认回调确实来自微信。
- 检查业务订单号是否存在。
- 检查订单状态是否是“待支付”,避免重复回调导致库存重复扣减。
- 更新订单状态,扣减库存,给用户加积分。
- 向微信返回
{"code":"SUCCESS"},否则微信会一直重复回调。
4. 管理后台与接口层的落地
4.1 PHP管理后台接口设计
管理后台用的是Vue + Element Plus这套组合,PHP侧负责给管理端提供接口。接口设计遵循RESTful风格,按资源拆分。
我举几个典型接口例子:
| 功能 | 请求方式 | 接口路径 | 说明 |
|---|---|---|---|
| 商品列表 | GET | /admin/products?page=1&keyword=精华 | 分页、搜索、筛选 |
| 新增商品 | POST | /admin/products | 传SPU基本信息+SKU数组 |
| 更新商品 | PUT | /admin/products/{id} | 整体更新 |
| 上架/下架 | PUT | /admin/products/{id}/status | 改变上下架状态 |
| 订单列表 | GET | /admin/orders?status=2 | 按状态筛选订单 |
| 发货 | POST | /admin/orders/{id}/ship | 填物流单号发货 |
| 用户列表 | GET | /admin/users | 用户分页查询 |
| 优惠券创建 | POST | /admin/coupons | 创建满减/折扣券 |
PHP侧我强烈建议用Laravel的Resource模式来输出JSON,它可以把输出结构统一成{ "data": {...}, "meta": {"page": 1, "total": 100} }这种。前端Vue只需要写一个统一的axios拦截器:
service.interceptors.response.use( response => { return response.data.data; // 直接取data层 }, error => { if (error.response.status === 401) { router.push('/login'); } return Promise.reject(error); } );这样前端所有接口调用都不需要重复处理异常,整体开发效率高很多。
4.2 接口鉴权与权限控制
后台接口不像C端那样用开放token,而是用JWT(JSON Web Token)+ 角色权限这套体系。管理员登录后,PHP签发一个JWT,里面包含uid和角色标识。中间件去解析和校验token。更细的权限控制建议做成RBAC模型(基于角色的访问控制),也就是“用户→角色→权限”三层结构。
权限粒度到“按钮级”在电商后台是有实际意义的。比如“商品管理员可以编辑商品但不能删除商品”,“运营专员只能看订单但不能退款”。后台菜单和前端路由可以按权限码过滤,Vue Router里通过meta.roles字段控制路由访问权限,配合v-permission自定义指令控制按钮的显示隐藏。
4.3 PHP与Node.js的接口协同模式
双后端之间是有明确分工的,但有些业务是跨端的,这个协同逻辑要提前设计好。比如商品数据管道:PHP管理端的新增商品接口,需要将商品主数据写入MySQL,并将商品牌照、详情等数据提交给AI审核平台做合规审核。如果审核通过,PHP需要把对应的商品上下架状态同步到Redis中,Node.js在处理小程序请求时并不直接查MySQL,而是优先读Redis里的商品热数据。
订单创建是另一个协同场景,实际流程是:
- 用户在小程序端把购物车提交为订单。
- Node.js创建订单号,锁定库存,生成待支付订单。
- Node.js请求微信下单并返回支付参数。
- 用户支付成功后,微信回调打到Node.js。
- Node.js修改订单状态,并调用PHP的接口同步订单数据到管理后台。
- PHP后台生成账单、佣金、报表。
这套链路里Node.js扮演的是“网关+核心交易”的角色,PHP是“后台管理+数据汇总”的角色,通过RabbitMQ或者Redis队列做异步解耦,避免高并发时PHP侧数据库压力过大。
5. 部署打包与常见问题排查
5.1 uniapp打包微信小程序全流程
常见的前端工程在HBuilderX里点“发行→小程序-微信”就能直接打包出dist/dev/mp-weixin或者dist/build/mp-weixin目录,然后微信开发者工具导入这个目录即可。但有几个细节特别容易踩坑:
- 微信小程序要求单包大小不能超过2MB(主包),如果资源过大需要分包加载。
- 静态图片建议全部上传到OSS/CDN,不要放在本地工程里,本地工程只留tabBar图标等必须资源。
- 项目如果开了ES6转ES5,某些写法(比如
async/await的深层嵌套)可能被转换出bug,建议在“本地设置”中开启“将JS编译成ES5”后重新试一下。 - 真机调试时的
request url必须配好域名白名单,开发工具可以勾选“不校验合法域名”,但真机上不配置一定不行。
HBuilderX打包成功之后,用微信开发者工具打开mp-weixin目录,需要配置自己的AppID。预览时可以看到模拟器的表现,真机预览依然要确保手机和电脑在同一个局域网内,否则扫码无法正常推送代码。
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | HBuilderX选择“发行” | 选择微信小程序 |
| 2 | 微信开发者工具导入dist/build/mp-weixin | 不覆盖AppID |
| 3 | 配置小程序后台的request合法域名 | 用HTTPS域名 |
| 4 | 点击“预览”生成二维码 | 真机扫码调试 |
| 5 | 上传代码并提交审核 | 体验版确认无误后提交 |
5.2 环境配置的三大经典坑
第一个是Node.js安装后npm命令无法执行,就是Windows上常见的那种报错:
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本这个绝大多数情况是PowerShell执行策略限制导致的。解决办法有几种:
# 当前用户允许执行脚本 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser或者改用管理员身份运行cmd,直接绕开PowerShell。还有一种可能是Node.js安装时没把node.exe的路径加到系统环境变量PATH中,需要手动在系统属性里配置C:\Program Files\nodejs。
第二个是PHP版本与Windows运行库不兼容,比如在Windows下运行PHP时报:
PHP Warning: 'C:\windows\system32\vcruntime140.dll' 14.0 is not compatible with this PHP build这是典型的VC运行库版本不对。解决方案是去微软官网下载最新的Visual C++ Redistributable for Visual Studio 2015-2022,选x64安装,重启PHP服务即可。另外PHP 8.x对操作系统的最低要求更高,Windows 7上经常跑不起来,建议直接用Windows 10/11 + PHPStudy或Docker部署。
第三个是数据库时区不一致导致订单时间错乱。PHP默认时区是UTC,Node.js默认跟随系统时区,如果MySQL的时区设置是SYSTEM,三个系统的时间就会对不齐。最稳妥的做法是在MySQL连接串里统一指定:
[mysqld] default-time-zone = '+8:00'同时PHP侧在入口文件设置date_default_timezone_set('Asia/Shanghai'),这样就保证了全链路时间一致。
5.3 微信小程序运行期常见问题速查表
开发过程中一定会遇到各种稀奇古怪的问题,我把几个高频的整理成一个速查表:
| 问题现象 | 排查方向 | 解决方案 |
|---|---|---|
| 预览白屏且控制台无报错 | 查看是否是App.vue里onLaunch的异步逻辑阻塞了页面渲染 | 把阻塞逻辑移到用setTimeout延后执行 |
接口报request:fail | 域名未在小程序后台配置或没有备案 | 本地关闭域名校验,线上配置HTTPS备案域名 |
| 获取手机号时按钮无反应 | open-type="getPhoneNumber"需要在真实微信环境测试,开发者工具会有限制 | 在真机模式下测试,或用uni.login配合后端code2Session换取 |
| 支付成功后订单状态不变 | 回调验签失败或回调地址未配置 | 检查支付回调URL是否公网可访问、签名参数是否一致 |
| 后台配置的商品在C端不显示 | Redis缓存未刷新 | 后台改完商品后调用一次刷新缓存接口,或设置缓存过期时间较短 |
uniapp编译后某些wxAPI不存在 | 平台条件编译未处理 | 用#ifdef MP-WEIXIN包裹微信独有API |
| 登录态失效后接口返回401 | token过期,前端未做更新 | 在axios/uni.request拦截器统一处理401,重新登录拿新token |
| 订单列表加载更多没反应 | onReachBottom在小程序中有条件限制 | 确认页面开启了"enablePullDownRefresh": true且使用了scroll-view时注意触发条件 |
| 商品详情视频无法播放 | 域名没有配置业务域名白名单 | 在后台配置“业务域名”,上传校验文件 |
| 管理端PHP接口跨域报错 | 后台接口和前端Vue域名不一致 | 设置CORS头:Access-Control-Allow-Origin、Allow-Methods、Allow-Headers |
还有一个很容易被忽略但很实用的小技巧:调试接口时一定先在微信开发者工具里的“Network”面板看请求,它显示的信息比uniapp控制台的全得多。特别是401、403这类状态码,可以快速定位是token问题还是权限问题。
5.4 个人实操体会:护肤商城内容运营的接口预留
最后说一点我自己的观察。做一个护肤购物小程序,商品卖得好不好,很大程度取决于“内容”做得好不好。很多技术出身的朋友容易忽略一个需求:商品详情页的“护肤资讯/成分解读”模块。
我强烈建议在系统设计阶段,就给“文章/测评/成分百科”这类内容模型预留数据表。前端在商品详情页会留一个“查看成分解读”的入口,点击之后进入富文本内容页,通过在商品ID与文章ID建立关联实现。这个小功能在技术上不复杂,就是content_id与product_id做关联查询,但实际运营中,品牌方会持续更新文章内容,同时还能带来小程序的搜索收录和分享传播。
另外建议对接一个简单的用户评价回复机制。护肤品的评价内容通常比较长,用户会认真写肤感、效果、过敏情况,这些内容对新用户的转化影响极大。后台需要在PHP管理端里给运营人员提供“回复评价”的功能开关,同时前端商品详情页按“好评优先/最新”两种排序展示评价列表,对维护品牌信任度很有价值。
做完这套系统再回头看,技术选型不是越新越好,而是越合适越好。PHP负责快速迭代后台逻辑,Node.js负责微信生态对接和C端稳定支撑,前端选uniapp则是为了未来多端复用留一手。这套组合经历了几轮开发迭代之后,已经被验证是护肤类电商小程序里非常能打的一套架构。如果有人要复刻这个项目,我的建议是:先别急着写代码,把商品数据模型和订单状态的流转逻辑在纸上画清楚,把双后端的数据边界、接口文档定义出来,再动手,后面能少走很多弯路。