客户说要做一个微信小程序版的电脑配件商城我第一反应是这不就是又一个商品列表加购物车的活儿吗直到翻到需求文档里“组装机配置”那一栏才意识到事情没这么简单。用户要在手机上像配电脑一样一个一个选CPU、主板、内存、显卡系统要实时算兼容性、算总价还要能一键把整张配置单加入购物车下单。这个玩法本质上不是卖配件而是做一套轻量的“DIY装机工具”。项目定了三件套后端用 Python前端用 uniapp目标端是微信小程序。正好之前一期交付的是微信小程序源码工程2048小游戏这一期从休闲游戏跳到交易类商城踩坑路径完全不一样。这篇文章就把整个项目的实现过程、技术选型逻辑和真实的坑位梳理一遍给后面要做同类型商城或者“配置器交易”结合场景的朋友做个参考。1. 项目骨架为什么后端选 Python前端选 uniapp1.1 后端用 Flask 而不是 Django 的真实理由我们这个项目后端团队就两个人一个写接口一个搞数据交付周期三周。Django 的 admin 后台确实香但它的 ORM、迁移、认证体系在项目前期反而是负担——我们要的是快速把接口跑起来而不是一上来就套重型框架。所以后端选了 Flask 加 SQLAlchemy配 SQLite 起步后期数据量上来再切 MySQL切换成本很低。具体到工程结构没有搞复杂的微服务就一个单体应用backend/ ├── app.py # Flask 入口注册蓝图 ├── models.py # SQLAlchemy 模型配件、规则、订单、用户 ├── api/ │ ├── parts.py # 配件列表、筛选、详情 │ ├── config.py # 组装机配置器的校验与价格计算 │ ├── cart.py # 购物车 │ └── order.py # 订单与支付参数 ├── utils/ │ ├── auth.py # JWT 鉴权 │ └── compatibility.py # 兼容性规则引擎 └── data/ ├── parts_seed.json # 配件种子数据 └── rules.json # 兼容性规则表Flask 的蓝图机制刚好够用每个业务模块一个文件不混乱。数据库表也不多配件表、类目表、兼容性规则表、配置单表、购物车表、订单表、用户表。配件表我特意用了一个 JSON 字段来存参数比如 CPU 的插槽类型、核心数、TDP显卡的功率、长度内存的频率和代数。因为配件参数五花八门规范化字段会把自己累死JSON 加索引反而灵活。1.2 uniapp 解决的不只是“多端复用”选 uniapp 最直接的理由当然是后续可能上 App 和 H5一套 Vue 代码不用重写。但实际做下来我发现它在这个项目里还有一个隐藏好处开发调试效率。微信小程序原生开发的编译器、热重载和调试体验说实话不如 Vue 的开发范式顺手。uniapp 的页面就是 Vue 单文件组件data、computed、watch都用得上对于配置器这种实时算价的场景响应式数据模型比小程序原生的setData舒服太多。你只要不碰document、window这些浏览器 API老老实实用uni.request、uni.navigateTo编译到小程序端基本不会出幺蛾子。顺便说一句很多人纠结 uniapp 和 uniappx 的区别。你要是只做微信小程序普通 uniapp 就够了要是想上安卓/iOS 原生性能才需要去看 uniappx。我们这项目没有重度计算普通的就行。1.3 用户端只交付微信小程序为什么还要管 App 的事有一点必须在项目启动时说清楚虽然这次只交付微信小程序但 manifest.json 里的 App 配置也要顺手做对。因为 uniapp 的云打包在配置 appid、图标、权限声明时如果漏了后面想补会很痛苦。我们的做法是开发期就把manifest.json的mp-weixin节点配齐包括小程序的 appid、requiredPrivateInfos如果需要位置等隐私接口。哪怕现在用不上也把基础项填了省得以后翻工。2. 组装机配置器整个商城最硬核的部分2.1 配件数据建模不能只当商品卖得让机器“认得”它普通商城把配件当商品标题、图、价格、库存就够了。但组装机配置器不一样系统必须能理解“这颗 CPU 配这块主板合不合理”所以配件数据要结构化。我的配件表核心字段长这样category类目标识cpu / motherboard / gpu / memory / storage / psu / case / coolername商品名比如“Intel 酷睿 i5-13600K 盒装”price售价单位分避免浮点误差paramsJSON里面按类目存放关键参数stock库存status上下架params里每类配件有约定俗成的字段比如{ cpu: { socket: LGA1700, tdp: 125, cores: 14, integrated_graphics: true }, motherboard: { socket: LGA1700, memory_type: DDR5, memory_slots: 4, form_factor: ATX }, gpu: { tdp: 320, length_mm: 336, power_connector: 3x8Pin }, case: { max_gpu_length_mm: 360, form_factors: [EATX, ATX, M-ATX, ITX] } }这里有个细节同一类目下的配件字段必须保持统一命名比如主板内存类型只允许DDR4或DDR5否则规则引擎没法跑。最开始我图省事同一个字段一会儿写memory_type一会儿写ram_type结果规则匹配时得做两套映射自我折磨。后来直接定死一份字段字典所有种子数据照字典填后面省心很多。2.2 兼容性校验不靠堆 if靠规则表驱动写配置器最容易掉进去的坑是校验逻辑全写在 Python 里来一个组合加一个 if。CPU 接口匹配一个 if内存类型匹配一个 if电源功率不够又一个 if……一开始确实爽等配件库扩到几百个 SKU 的时候函数会膨胀到没法维护。我换成了规则表驱动。核心思路是“校验动作可配置”每种兼容性检查对应一条规则规则里有检查类型、涉及的两个配件类目、判定方式。具体到代码compatibility.py里只留了规则执行器def check_compatibility(selected_parts, rules): selected_parts: dictkey 为类目名value 为配件对象 rules: list每条规则包含 check_type, part_a, part_b 返回所有不通过的校验结果 results [] for rule in rules: checker COMPATIBILITY_CHECKERS.get(rule[check_type]) if not checker: continue part_a selected_parts.get(rule[part_a]) part_b selected_parts.get(rule[part_b]) if not part_a or not part_b: continue ok, message checker(part_a, part_b) if not ok: results.append({ message: message, level: rule.get(level, error) # error 硬性不兼容warning 提醒 }) return results然后定义若干 checker 函数def check_socket(cpu, motherboard): if cpu[params][socket] ! motherboard[params][socket]: return False, fCPU 插槽 {cpu[params][socket]} 与主板插槽 {motherboard[params][socket]} 不匹配 return True, def check_memory_type(motherboard, memory): if motherboard[params][memory_type] ! memory[params][memory_type]: return False, f主板支持 {motherboard[params][memory_type]}内存是 {memory[params][memory_type]} return True, def check_psu_power(cpu, gpu, psu): required cpu[params][tdp] gpu[params][tdp] 80 if psu[params][wattage] required: return False, f电源 {psu[params][wattage]}W 不足以支撑整机功耗建议 {required}W 以上 return True, 规则表存在rules.json里每条就是一个声明[ {check_type: socket, part_a: cpu, part_b: motherboard, level: error}, {check_type: memory_type, part_a: motherboard, part_b: memory, level: error}, {check_type: psu_power, part_a: psu, part_b: gpu, part_a2: cpu, level: error}, {check_type: case_gpu_length, part_a: case, part_b: gpu, level: warning} ]以后新增规则比如电源线材接口不对只需要加一个 checker 函数和一条规则声明不用动前端和主流程。我用“错误”和“警告”两级错误会阻止用户加入配置单警告只提示不拦截——比如显卡太长塞不进机箱我会警告但允许用户继续因为确实有人强行上开放式机架。2.3 前端交互选一个刷新一批响应式数据模型立功配置器页面我设计成三栏结构左边是配件类目CPU、主板、内存……中间是当前类目的配件列表底部是常驻的“已选配置单”栏实时显示总价和兼容性状态。手机屏幕小左右结构用 tab 切换实现底部栏用position: fixed。用户每点一个配件前端就把已选的所有配件 ID 发给后端/api/config/check接口后端返回总价、兼容性错误列表、每项配件的状态正常/缺货/不兼容。这里必须定一个原则价格计算和校验以后端为准前端只负责展示。你不这么做的话用户改一下前端代码就能把价格改掉订单金额对不上财务那边会骂人。接口数据结构大概是{ total_price: 859900, parts: { cpu: {id: 101, name: Intel 酷睿 i5-13600K, price: 249900, status: ok}, gpu: {id: 205, name: RTX 4070 SUPER, price: 489900, status: warning, warnings: [显卡长度可能超出机箱限长]} }, errors: [CPU 插槽与主板插槽不匹配] }前端拿到之后用 Vue 的响应式数据整体替换底部栏状态价格和红色感叹号马上刷新。uniapp 这边有个小坑scroll-view里的列表如果数据量超过几百条渲染会有明显卡顿。配件库 SKU 虽然有上千个但用户一次只看一个类目所以我做了分类目分页一页 20 条onReachBottom触底加载下一页性能没出过问题。3. 商城基础链路从商品浏览到订单支付3.1 商品浏览、搜索和筛选的最小闭环配置器不是全部商城该有的基础功能一个不能少。配件列表页和配置器中间件共用一套后端接口只是展示维度和筛选项不同。筛选项我用 URL query 实现比如/api/parts?categorygpubrandNVIDIAsortprice_ascpage1page_size20后端用 SQLAlchemy 动态拼接查询条件注意排序字段要用白名单校验不能直接拼进 SQL 里。brand、socket、memory_type这些筛选项来自前端提交万一有人传一个order_byprice; drop table之类的参数拼接进去就是事故。白名单一拦现场就没了。搜索功能没有上 Elasticsearch配件标题本身不长用LIKE %关键词%够用。量大了以后可以换 SQLite FTS5 或者 MySQL 全文索引初期没必要引入额外组件。3.2 购物车既要装单品也要装整张配置单购物车是这个项目里比较容易想简单的地方。普通商城购物车只装商品我们的购物车有两种行单件配件和整张配置单。我建了一张cart_item表iduser_iditem_typepart 或 configpart_id当 item_type 为 part 时有值config_snapshot当 item_type 为 config 时存整个配置单快照JSONquantitycheckedcreated_at关键在设计config_snapshot。用户把一张配置单加入购物车之后配件价格和兼容状态必须冻结在快照里不能等用户下单时再去现查配件表。否则用户加购时显卡卖 4899等三天后下单时已经涨到 5299你按哪个价格收钱按现价收用户投诉按旧价收你亏钱。快照方案逻辑最简单加购即冻结。配置单快照的 JSON 大概长这样{ parts: [ {category: cpu, id: 101, name: Intel 酷睿 i5-13600K, price: 249900}, {category: gpu, id: 205, name: RTX 4070 SUPER, price: 489900} ], total_price: 859900, compatibility_status: ok }下单时直接用快照金额不跟实时价格联动这才是交易系统应该有的确定性。3.3 订单状态机与微信支付的认证门槛订单状态我定了五个待支付、已支付、备货中、已完成、已取消。后端用一个status字段加一个允许状态流转的映射表防止接口被乱调用跳状态。比如从“待支付”只能走到“已支付”或“已取消”不能直接跳到“已完成”。微信支付这块有个必须先讲清的现实问题个人主体的小程序不能开通微信支付必须是企业主体或个体工商户。如果你是自己练手项目可以用微信支付沙箱环境模拟如果是给客户做交付必须提前确权客户的资质否则做到一半发现支付接口申请不下来整个项目白做。我们这次客户是有企业主体的走了正常的商户号申请流程前后大概一周时间。支付流程用 uniapp 的uni.requestPayment封装一层后端order.py里创建订单后调用微信支付统一下单接口拿到paySign返回给小程序端拉起支付面板。回调地址注意要配到微信商户平台而且回调处理要幂等同一个支付结果通知可能推多次处理前先查订单状态避免重复更新。4. 微信小程序端的适配与真机调试坑4.1 自定义导航栏胶囊按钮的“毛边效应”我们这个小程序没有用默认导航栏而是自定义了顶部导航为了把“配置器已选数量”这种信息塞进导航栏右侧。自定义导航栏的第一个坑就是不同机型的胶囊按钮位置不一样。iPhone 上有灵动岛的机型和老款刘海屏胶囊按钮的top和height都不一样。官方 API 是uni.getMenuButtonBoundingClientRect()能拿到胶囊的位置和尺寸但不能直接把它写死到样式里。我是这样算的const menu uni.getMenuButtonBoundingClientRect() const navBarHeight (menu.top - statusBarHeight) * 2 menu.height道理很简单胶囊按钮垂直居中的位置决定了整个导航栏的视觉中心用胶囊的 top 减去状态栏高度再乘以 2 加自身高度基本就是自定义导航栏的标准总高。两侧留白也要动态算不能写死 100rpx因为不同机型胶囊到屏幕右边界的距离不一样。4.2 手机号登录新接口必须在用户点击后才能调配置器依赖用户登录后才能保存配置单所以登录环节绕不开。微信小程序获取手机号的接口在 2023 年之后改了规则不能直接wx.getPhoneNumber拿号码了必须用open-typegetPhoneNumber的按钮组件用户主动点击触发才能拿到code然后后端用code换手机号。前端代码大致是button open-typegetPhoneNumber getphonenumberonGetPhoneNumber微信一键登录/buttononGetPhoneNumber里拿e.detail.code传给后端后端拿 code 去微信接口换手机号再用手机号关联用户。这里有个容易踩的坑e.detail.code是一次性的每点一次换一个新 code后端必须一次性用完不能缓存。第一次没调对接口参数code 就废了用户只能再点一次按钮体验很裂开。4.3 uniapp 开发者工具里“日志不见了”的排查方法开发到中期我遇到一个特别玄学的问题uni.request 的 success 回调里明明有数据但console.log在微信开发者工具的 Console 面板里死活不显示。网上查了一圈有人说是 uniapp 编译模式问题有人说是工具缓存问题。试了一圈管用的操作是把开发者工具的“ES6 转 ES5”选项关掉再重新打开然后清缓存重新编译。这里背后原因其实是部分 console 语句被 babel 转换后丢掉了 sourcemap 关联重新编译能恢复。另外真机调试时日志不显示通常是打开了“过滤无来源日志”之类的选项。开发者工具左上角有个日志过滤按钮默认会隐藏一堆插件日志把级别调到 All再去 Console 面板看基本就出来了。5. 打包交付与后端部署要点5.1 从 HBuilderX 到微信开发者工具代码上传四步走uniapp 项目写完之后交付给客户时一般走这套流程HBuilderX 里选运行 - 运行到小程序模拟器 - 微信开发者工具会自动拉起微信开发者工具并加载编译后的dist/dev/mp-weixin目录。确认调试没问题后选发行 - 小程序-微信生成生产环境包。在微信开发者工具里点“上传”按钮填版本号和备注把代码传到微信后台。后台提交审核审核通过后可以发布体验版或正式版。这里提醒一句manifest.json里的微信小程序 appid 必须填对否则上传时会被后台拒绝。很多人开发时用的测试号 appid到正式上线时忘了替换浪费一个审核周期。5.2 后端部署域名、HTTPS、备案一个都不能少微信小程序发正式版时所有请求域名必须是 HTTPS 且已备案这是硬性要求。我们后端部署在云服务器上用了 Nginx 加 Let’s Encrypt 免费证书。Nginx 配置里转发了/api/路径到 Flask 的 5000 端口同时配了 gzip 压缩对小程序的请求体很有帮助。还有个安全细节后端接口要校验请求来源最简单的方案是加一个自定义 Header比如X-App-Client: mini-programNginx 层拦截没有这个 Header 的请求。防不住高级攻击但能挡住扫描器和乱爬的脚本。5.3 这套代码后续还能扩展什么交付之后客户问能不能加“智能推荐配置单”其实底层已经具备条件。配置器收集了很多用户选配数据后端可以统计每个价位段里哪些配件组合最常同时被选中给新用户推荐几套“套餐配置”。这就是最简单的协同过滤用 Python 的 pandas 处理历史配置单跑个关联规则不复杂但效果很直观。另一个方向是库存预警。配置单生成之后可以检查所有配件的库存状态只要有配件缺货整个配置单就标成“不可下单”。这个逻辑放到快照生成时同步执行不用等下单再报错。我个人在实际操作中的体会是这种“工具商城”的复合项目真正拉开差距的不是商品 CRUD而是配置器这种带业务逻辑的模块。它逼着你把数据模型设计得足够规范把校验逻辑抽象成可配置的规则否则配件库一扩代码全是补丁。如果让我重做一遍我大概会把兼容性规则从 JSON 换成数据库表加上后台管理界面让运营自己就能录入新规则不用每次改代码发布。这是下一期迭代最值得做的优化。