做护肤类小程序购物系统,和做普通电商小程序完全是两码事。表面看都是“商品列表加购物车加下单”,但护肤品的品类逻辑、用户决策链路和复购场景,比标品电商复杂得多。这个项目我前后改了四版,从最初“套模板的商城”到最终“带肤质测试的选购闭环”,踩了不少坑。这篇文章就把整个设计与实现过程拆开讲清楚,从需求拆解到数据库设计,再到微信登录、支付回调、订阅消息这些关键环节的实操细节,顺带把新手最容易翻车的地方一并列出来。
1. 项目设计与核心思路
1.1 先搞清楚“精致护肤”到底在卖什么
做这类系统之前,必须想明白一件事:护肤品的购买逻辑,和买衣服、买零食不一样。用户在淘宝买一件T恤,决策链路是“看着好看—看尺码—下单”;但买一瓶精华,用户会先想“适不适合我的油皮、会不会闷痘、和现在用的水乳冲不冲突”。如果只是一个普通商品列表,用户进来看一圈就退了,根本留不住。
所以这个项目的核心思路,不是“做一个卖护肤品的商城”,而是“做一个能告诉用户该买什么护肤品的工具”。我把整个购物流程拆成三个层级:
- 选品辅助层:肤质测试、成分标签、功效筛选,帮用户建立“我适合什么”的认知。
- 交易闭环层:商品详情、购物车、订单支付、物流跟踪,完成从种草到收货的完整链路。
- 信任维护层:购买后的护肤建议、订阅消息提醒、定期回购引导,这是复购的关键。
这套设计逻辑,直接影响了下文要讲的所有表结构、接口设计和页面跳转方式。如果一开始只是照着电商模板抄,后面想加“肤质推荐”功能,你会发现表之间根本关联不上,改起来等于重构。
1.2 技术选型:原生小程序还是 UniApp,我是怎么定的
这类项目被问到最多的问题之一就是技术栈选择。市面上主流方案有两套:微信原生小程序,和 UniApp 跨端框架。我最终选了原生小程序,不是因为它最好,而是因为这个项目有几个实际情况:
| 对比维度 | 原生微信小程序 | UniApp |
|---|---|---|
| 运行性能 | 组件渲染更直接,长列表滚动更稳 | 多一层编译和桥接,复杂页面有性能损耗 |
| 微信API适配 | 原生支持,新接口能第一时间用上 | 依赖插件生态,个别能力要等兼容更新 |
| 学习曲线 | 上手简单,没有框架转换成本 | 需要额外理解Vue语法和编译规则 |
| 跨端能力 | 只能跑微信,没有跨端优势 | 同一套代码可编译到支付宝端、H5 |
| 调试体验 | 微信开发者工具直出,靠谱 | 经常要处理“开发环境正常、真机异常”的问题 |
从实际测试来看,原生方案的运行流畅度和接口测试的直观程度都有优势,非常适合毕设或个人项目的需求。如果着眼未来想同时上支付宝端,风险点主要在选择UniApp后,要面对video组件在各端兼容性不一的问题。这个项目的核心是搞定微信生态内的自有能力,把原生玩明白是更稳的选择。
1.3 页面结构设计避坑:tabBar 数量和皮肤测试的入口
接下来是页面架构方面的决策。零食电商小程序会先用小程序体验版收集试用测试,不断调整页面设计,而这个项目则从一开始就明确了核心流程。
我的 tabBar 设置了四个入口:首页、分类、购物车、个人中心。这是刚需,不用纠结。真正难的是把“皮肤测试”功能藏在哪里。一开始我把测试入口放在首页轮播图下面的一个金刚区,结果用户根本不点——因为大家对“测试”这个词无感,他们想看的是产品。
后来我把测试改成“AI测肤”的引导卡片,放在搜索框正下方,文案直接写“30秒找到适合你的精华”,点击率一下子就上来了。这里有一个产品层面的洞察:用户不关心技术是什么,只关心“这个功能对我有什么用”。测肤入口的文案和位置,直接影响整个推荐逻辑能不能跑起来。
首页的信息流结构我建议从上到下依次是:搜索框、测肤引导卡、轮播图(放品牌活动和主推品)、金刚区(领券、签到、订单查询)、为你推荐(基于肤质测试结果的商品瀑布流)。这个顺序是符合决策心理的——先建立“这个平台懂我”的信任,再给优惠刺激,最后才上商品推荐。
2. 数据库设计与核心表结构
2.1 用户、肤质与会员体系的关联设计
这个项目的数据库设计,是我认为最有参考价值的部分,因为普通电商的表的字段根本不够用。用户表(user)除了常见的 openid、nickname、avatar 之外,还必须预留肤质信息。
我的实现方式不是直接在 member 表里加几个字段,而是单独拆出一张 skin_analysis_record 表。每一次测肤,生成一条记录,包含以下核心字段:
- user_id:用户ID
- skin_type:肤质结果,指干性、油性、混合性、敏感性
- skin_score:综合得分
- concern_tags:用户勾选的困扰项,如干燥起皮、出油、泛红敏感
- raw_data:原始答题数据,JSON格式冗余存储
- advice_snapshot:推荐方案的快照,JSON格式
为什么要存 JSON 快照?因为推荐策略以后会改,但用户历史测试结果要保持原样,否则“三个月前测过什么、推荐了什么”就说不清了。这些数据也会驱动后续的个性化推送和商品推荐。
建议也是单独存储的,因为一次测肤可能会出多套方案。最终用户主动选择的方案,会关联到复购提醒逻辑,在不同场景下派发对应的优惠券或回访消息。
2.2 商品表、SKU 表与“适合肤质”标签
护肤品的商品和服装不同,核心 SKU 维度不是颜色尺码,而是规格(30ml/50ml)和套装组合。在设计商品表时,要重点考虑一个细节:护肤品的“适应性”是多对多的。同一款精华,可能同时标着“适合油皮”“适合混油”;同一款面霜,可能“干皮适用”也标注了“敏感肌慎用”。如果只是简单地在商品表加几个标签字段,后期维护分类列表会变得异常痛苦。
我的做法是拆三张表:
- goods(商品主表):存标题、主图、副图、详情、品牌ID、上下架状态。
- goods_sku(规格表):存规格名、价格、库存、SKU编码,通过 goods_id 关联主表。
- goods_suit_skin(商品—肤质关系表):即多对多的关联表,存 goods_id、skin_type(干/油/混合/敏感)。
当用户测完肤质,系统拿到 skin_type 结果,直接关联皮肤匹配表,查询所有适配的商品。这里有个细节要注意:查询的返回结果一定要做去重和排序,否则同属油皮和混合皮的商品会重复出现,列表也会乱掉。排序逻辑建议这样设计:完全匹配的排最前,部分匹配的排中间,普通商品按销量排最后,这样既不浪费库里的商品,也不会给用户完全失配的推荐。
2.3 订单表与状态机:七种状态的流转路径
订单表我用 status 字段管理状态,取值定义为:
| 状态值 | 含义 | 可操作动作 |
|---|---|---|
| 0 | 待支付 | 取消订单、去支付 |
| 1 | 待发货(已支付) | 申请退款 |
| 2 | 待收货 | 确认收货、申请售后 |
| 3 | 已完成 | 申请售后、再次购买 |
| 4 | 已取消(用户主动) | 无 |
| 5 | 已关闭(超时未付) | 无 |
| 6 | 售后中 | 撤销售后申请 |
这里最容易踩坑的地方在于:微信支付的异步回调有延迟,而且是“多端多个事件同时触发”。用户可能在客户端点了支付按钮,服务端同时收到支付成功回调,这时候如果你的代码直接把订单状态从“待支付”改成“已支付”,然后客户端页面还停留在“待支付”界面,就会出现闪烁或状态不一致的问题。
我最终的做法是:所有订单状态变更业务逻辑全部放在服务端处理,小程序端只负责展示服务端返回的订单状态。并且统一收口到一个订单状态更新接口,每次变更都写一条操作日志,方便排查用户和客服争议。
3. 核心功能模块的实操实现
3.1 微信登录到手机号绑定的完整链路
登录逻辑在小程序端是最绕的,因为微信的升级导致登录流程发生了很大的变化。现在我常用的方案是三步走:
第一步,通过 wx.login() 获取临时 code,通过后端接口换成 openid。这里要明确一个点:现在的 code 换 session_key 有严格次数限制,不能反复调用 wx.login(),否则可能拿到无效 key。
第二步,调 wx.getUserProfile() 获取头像昵称并录入用户表。这个接口在较新版本的基础库中已经不支持主动弹窗了,真实体验是用户到个人中心点击头像时,才会触发授权。
第三步,引导用户在小程序内点“允许获取手机号”的按钮,通过 getPhoneNumber 获取手机号。服务端拿到 code 后,再调用接口换取真实手机号。
接口数据结构大致是:
// 前端获取手机号 wx.getPhoneNumber({ success: (res) => { // res.code 是动态令牌,不是手机号本身 wx.request({ url: 'https://api.example.com/user/bindPhone', data: { phoneCode: res.code }, success: (resp) => { // 服务端通过 phoneCode 换取手机号并绑定 } }) } })有一个经验值得分享:不要一开始就让用户授权手机号,太早了反而容易吓跑用户。更稳妥的做法是让用户先逛、先加购物车,等真正要下单结算时,再引导绑定手机号。下单环节是用户目的最明确的时候,这时候提出绑定请求,完成率反而高得多。
3.2 购物车结算:价格计算与库存锁定
购物车的接口设计也有讲究。我用的数据结构是:
{ "cartItems": [ { "skuId": "G888-SKU01", "quantity": 1, "checked": true } ], "totalAmount": 399, "freightAmount": 0, "discountAmount": 0, "payAmount": 255, "couponId": "C1024" }结算有个关键点:不能直接把 前端传回来的 totalAmount 当作最终支付金额,那太容易出问题。前端可能被篡改,也可能有缓存数据,导致价格对不上。正确的做法是在后端重新计算一次订单总价,前端传过来的所有金额字段都只作为展示建议。
库存处理上也建议用“预占库存”的方案:支付成功之前锁定库存,超时未支付自动释放。而不是“下单就扣库存、取消再返还”,这种方案在并发高时容易超卖且不好追溯。预占模式下,订单待支付期间如果用户反复进出结算页,每个请求都对同一组 SKU 预占多次,这时候要用幂等键来控制,同一个用户同一批 SKU 只预占一次。
优惠券的叠加规则,在护肤类商城比普通电商更容易出错。因为经常有“满399减60”和“买精华送小样”两类活动同时进行,叠加或互斥规则必须在服务端校验,例如:同一订单只能用一张平台券,品类券和平台券可以叠加,但叠加后的最终到手金额不能低于设定的压线价格。
3.3 支付流程接入:下单接口、支付参数与回调验签
支付接入的流程是这样的:前端提交订单,后端生成订单号,调微信支付统一下单接口,拿到支付参数,再让前端用 wx.requestPayment() 拉起支付。
这里坑最多的环节是回调验签。微信支付的回调是 POST 到你自己配置的回调地址,里面带上了所有订单信息和一个签名。一定要用微信官方 SDK 的验证方法去校验签名,不能自己手写逻辑,否则一旦数据被伪造,订单状态被篡改甚至用户资金受损,后果非常严重。
另一个容易出问题的地方是回调接口的幂等性:同一个支付结果微信可能推送多次,第一次处理完订单之后,后续请求必须直接返回成功(告诉微信“不要再推了”),否则微信会一直重试,导致订单重复处理或日志刷屏。支付回调处理完业务之后,如果没有返回约定的成功报文给微信服务器,它就会不断重试,这个细节我会经常在项目里写上“必须注意”。
此外,支付成功后一定要给用户一个明确的展示反馈,例如支付结果页展示“支付成功”状态、订单编号、预计发货时间。用户更需要的是确定感,而不是跳回首页了事。
3.4 订单列表与“加载更多”分页避坑
订单列表、商品列表都涉及分页,这在真实项目里是高频点。最稳妥的分页 Scheme 是游标分页,而不是传统的 page 分页。游标分页的好处是,当列表中间插入新数据时,不会出现重复或漏数据。
接口设计:
// 请求参数 { "pageSize": 10, "cursor": "20250401xxxxxx" } // 返回数据 { "list": [...], "nextCursor": "20250402xxxxxx", "hasMore": true }小程序的 page 滚动加载更多,常规实现是监听 onReachBottom 事件,触底时请求下一批数据。但这里有几个实际问题要注意:
- 如果加载速度太快,容易重复请求,需要加一个 loading 状态锁。
- 页面数据量到一定量之后,要考虑分页渲染或虚拟列表,否则页面会卡顿严重。
- 订单列表和商品列表的自定义样式要保持一致,避免用户在不同页面体验割裂。
我当时实测过一个很让人头疼的现象:订单列表一次性渲染 50 条记录,旧版安卓机上滑动就掉帧。后来把每页改成 10 条、加 loading 状态提示“加载中”,配合 setData 只更新新增部分,滑动就顺滑多了。这里想强调的是,优化性能核心不是靠“减少数据量”,而是靠“控制页面上的 setData 数据体量”和“减少 WXML 节点复杂度”。
4. 常见问题与排查技巧
4.1 页面加载后白屏,或部分样式错乱
这个问题的根因常常不是代码,而是“分包加载”或者“全局样式污染”。在原生小程序里,定义的公共样式如果在 app.wxss 里写得过于粗暴,很容易覆盖页面级样式,或者不同页面之间由于样式命名冲突,出现界面错乱。
排查方法建议从三处入手:
第一,检查 app.json 里的页面注册顺序,默认展示的首页路径是否在 pages 数组的第一位。 第二,检查根节点的用户信息是否在小程序启动时异步加载成功,如果数据未就绪就渲染,页面会先空白再跳变,观感很差。 第三,检查公共样式文件,特别是 button 的默认样式重置,很多商城项目为了好看重写了 button,结果订单页的按钮和支付按钮形态全歪了。
以上问题,最终可以通过“页面加载时的骨架屏”以及“公共样式控制粒度”两条路来一起规避。
4.2 订阅消息只能发一次?正确流程要这样设计
我经常看到刚做小程序购物的人抱怨:订阅消息怎么只能发给用户一次?这个设置源于微信的设计限制,因为“一次性订阅消息”确实是一次授权一次下发,无论用户点了多少次弹窗,每次授权只能让开发者发一条。
订单场景下的正确流程应该是这样的:用户下单支付完成后,引导用户勾选订阅“发货通知”,获得一次授权,订单发货后向用户发送一条“您的订单已发货”。如果需要“签收提醒”或“会员日提醒”,就得在相应流程里再次引导点击授权。
技巧在于:授权弹窗不能“无脑弹”。比较有效的触发点是核心业务动作。例如用户提交订单成功后,弹出“订阅消息授权”,文案明确写“允许我们将发货信息推送给您”,接受率会高得多。等发货后这条消息发出了,用户正好需要这个信息,也会更容易信任这个平台,后面做复购唤醒就更顺了。
4.3 微信开发者工具正常,真机白屏或接口失败
这类问题高频出现,且最浪费时间。常见根因有三个:
第一,域名白名单。微信小程序在开发工具里可以设置“不校验合法域名”,所以工具里畅通无阻,真机上却直接拒绝请求。开发阶段可以临时打开不校验,但上线前必须在小程序管理后台配置 request 合法域名,且该域名必须已备案、支持 HTTPS。
第二,IP 直连访问也会在真机上被挡,必须使用域名。
第三,本地研发时的 localhost 接口,真机无法访问。真机调试时需要用局域网 IP,且微信开发者工具的“不校验合法域名”选项,不覆盖真机环境。
至少我在这个项目里,微信开发者工具里跑得好好的,一上真机请求全失败,排查到最后就是域名没备案导致的黑屏拒绝。这块在部署上线前,一定要尽早申请域名并完成备案,预留足够时间。
4.4 支付金额不对?多半是前端参与计算了
“前端算金额”是我强烈建议避开的点。优惠券、满减、积分抵扣这类计算,如果让前端按本地数据来算,会带来两个隐患:一是前端代码被打包后容易被逆向分析,计算规则直接暴露;二是新老版本小程序并存的时候,后端改了规则,老用户拿旧版本的计算逻辑,会出现金额不一致。
最稳的做法是服务端统一计算,前端展示时,直接展示服务端返回的 totalAmount 即可。确认订单时,前端传商品 ID 列表和 skuId 列表,后端在读取出最新价格后,计算实付款返回给前端。
另外,针对测试环节,如果在开发期间反复切换微信号测试,订单历史记录会夹杂多账号数据。这时候要单独设计一套“测试账号”机制,比如用 test+时间戳的方式生成订单,或者干脆用独立的测试环境库,避免测试数据和线上正式数据混在一起,后续对账特别痛苦。
4.5 微信支付回调丢失或重复怎么办
回调丢失的原因通常在于网络抖动后微信重试,以及回调地址没返回正确的success响应。处理这个问题的要点分三点:
第一,确认回调地址是公网可访问的 HTTPS 域名。 第二,回调业务处理要“查单 + 更新”两步走。收到回调先查本地订单是否存在,存在且已经是已支付状态,就直接返回成功,不再重复改状态。 第三,服务端额外做一个定时任务,每隔一段时间主动查一次微信支付的订单状态,把“支付了但没收到回调”的订单自动补上。这样即使回调全丢了,也能兜底。
这个方案在正常支付流程下看起来有点“多余”,但在双11这种时段,或者微信支付偶尔不稳定时,它是必须的保护措施。
5. 项目部署上线流程与小技巧
5.1 服务器部署与域名备案顺序
整个项目前后端分离,前端托管在小程序平台,后端部署在云服务器。部署流程有个讲究:要先申请域名并完成备案,再配置 HTTPS 证书,最后才能在小程序后台配置 request 合法域名。这个顺序反了,后面就得返工。
服务器规格方面,初期用户量不大时,2核4G的云服务器配一个 MySQL 数据库就够用。如果后面并发上来,再加缓存层和负载均衡。项目规模的判断标准非常简单:用户量和订单量远没达到需要集群的时候,不要过度设计,否则不仅费钱,还会给自己增加系统复杂度。
5.2 体验版、测试版与审核上架流程
小程序开发完成后,流程是这样的:上传代码 → 提交审核 → 审核通过后发布上线。
在提交审核之前,一定要先设置体验版并邀请几个朋友实测,因为“开发者看着没问题”和“用户实际体验良好”是两个概念。重点测试环节包括:老版本基础库上的兼容性、不同手机屏幕的适配性、弱网环境下的加载表现。
审核的时候,如果涉及“在线销售”类目,部分类目需要提供相关资质,护肤品类目资质审核尤为严格。不提前准备齐全资质,整个提审流程都会被卡住。不同化妆品类目对应的资质要求不同,建议提审之前仔细读一遍官方的类目资质文档,别等项目开发完才开始补材料,那会耽误很长时间。
5.3 运营阶段的复购与用户触达
小程序商城上线后,比拉新更需要完善的是复购链路。在护肤品类目上,我的实操经验是上线后的第二个月开始做“周期购”功能:用户选择某个产品后,设定为每30天自动复购,价格比单次购买便宜一点。这样既锁定稳定销量,又简化用户购买路径。
同时在用户“买了之后”的第七天,系统自动推送一条护肤小贴士和本周精选专题。因为普通用户可能早就忘了自己买过什么,这时候推一条“你上次购买的XX精油,搭配XX使用效果更好”,转化率比盲推商品高很多。这类提醒可以通过订阅消息或微信客服消息实现,前提是导购流程中要设计好订阅场景。
6. 项目经验总结与后续扩展方向
这个项目做下来,我心里最大的体会是:小程序购物系统开发的实际难点,大多不在“写代码”上。代码框架、页面组件、接口调用这些东西,本质上都有成熟模板可循;更花精力的是把业务细节想透,例如订单状态边缘场景怎么处理、支付回调等外部依赖如何兜底、推荐逻辑是否真的帮助用户更快做决策。
一个小程序商城,代码级别的工作量可能两三个月就能完成,但如果把“推荐逻辑为什么有效”“用户为什么愿意回来”这些都装进去,工作量就没有上限了。
后续扩展方向上,我认为最值得做的是三件事:
第一,把测肤结果从“一次性推荐”升级为“持续追踪”。用户在平台的每一次测肤,都记录下来,按时间线展现肤质变化,比如油脂分下降、敏感度降低。这会形成独特的用户资产,也是普通平台无法提供的深度体验。
第二,接入客服消息能力。用户在订单详情页遇到问题时,有时并不想跳转到小程序客服工具,更希望在聊聊订单的同一会话里把问题解决。接入客服消息双向通道,让用户在“我的客服”里直接聊,体验会好很多。
第三,做轻量级用户画像。通过测肤历史+购买记录+浏览行为,沉淀每个用户对“成分偏好”和“价格区间”的倾向。后续每次做促销活动,就可以按画像做对象筛选,从“全量推送”升级成“精准推荐”,整体转化率会有明显提升。
这些功能我不会建议一口气全做完。小程序项目最忌讳“大而全”,一个版本只做最核心的一件事,把这一件事做到极致,用户体验和后台压力都可控。这也是我在这类项目上一路走过来的最大心得。