做这个 Python + uni-app 组合的微信小程序「电脑配件商城组装机配置」,起因特别朴素:身边想自己动手装机的人不少,但真正能看懂插槽、功耗、板型的没几个。有人在电商平台把 CPU 和主板都加进购物车了,最后发现插槽对不上只能退货;有人机箱买小了,显卡塞不进去。我当时的思路很直接——把商城和组装机配置器放进同一个微信小程序,让用户像拼积木一样选配件,系统实时告诉你哪里不兼容、功耗够不够、总价多少,选完直接下单,后端用 Python 提供接口,前端用 uni-app 一次性覆盖微信小程序和后续的 H5、App。这篇文章把整个实现过程拆开讲,包括后端接口设计、配件数据建模、兼容性校验算法、uni-app 页面交互,以及微信登录、支付、上线审核中的各种坑,适合正在做电商或工具类小程序的开发者参考。
1. 这个项目到底要解决什么问题
1.1 不只是商品列表:组装机配置器才是核心
如果只做一个普通的电脑配件商城,完全没有必要单独写一篇总结——无非商品列表、详情、购物车那一套。真正让这个项目有门槛的是"组装机配置器":用户需要在 CPU、主板、内存、显卡、硬盘、电源、机箱、散热器八个品类里分别挑选一件,系统要能判断这一整套方案是否装得上、带得动,并实时给出总价和功耗建议。
装机踩坑的场景其实就那几类,我归纳下来有:
- CPU 和主板的插槽不匹配(比如 LGA1700 的 CPU 配了 AM4 的主板);
- 内存代数不匹配(DDR4 主板插了 DDR5 内存);
- 机箱太小,显卡太长塞不进去,或者电源位放不下;
- 电源瓦数不够,整机满载以后直接重启保护;
- 风冷散热器太高,盖上侧板之后顶到机箱。
这些问题对一个懂行的 DIY 玩家来说不算事,但对刚入门的人就是致命伤。配置器要做的就是把这些规则变成代码,在用户选件的过程中实时反馈,让"不懂装机"的人也能大胆下单。所以从功能优先级上,我把配置器排在最前面,普通商城排在后面。
1.2 目标用户与使用场景拆解
这个项目的用户画像有三种,对应不同的功能设计:
第一类是装机小白,占比最大。他们的诉求是"我预算 6000 块,帮我配一台能玩主流网游的主机"。我给配置器设计了两种入口:一是完全手动,从头选八件;二是在推荐配置单基础上微调,比如先选一个 5000 元整机方案,再在里面换一块更好的显卡,系统会重新校验兼容性并刷新总价。
第二类是懂行的进阶玩家,他们知道自己要什么,更需要的是"查漏补缺"。这类用户看重配件详情页有没有写清楚插槽、供电、散热限高这些参数,所以细节页的信息层级一定要清楚。
第三类是运营管理员,他们的需求也比较明确:调整配件价格、上下架、维护推荐配置单。这部分我做了个简单的 API 给后台用,没有做独立的后台页面,数据维护直接通过接口完成。
明确了这三类场景以后,技术选型和数据建模才有方向,不然很容易做成一个看起来功能齐全、实际用不起来的玩具。
2. 技术栈选择:为什么是 Python + FastAPI,为什么偏偏 uni-app
2.1 后端选型的真实原因
后端我选了 Python 和 FastAPI。有人会问,前后端分离的商城项目为什么不用 Java 或者 Go?答案很实际:团队里 Python 最熟,FastAPI 写 CRUD 和校验逻辑特别快,而且自带基于 OpenAPI 的接口文档,小程序端对接接口的时候直接看文档就能把参数对清楚。
FastAPI 的几个特性在这个项目里很实用:
- 基于 Pydantic 的请求参数校验,前端传过来一个非法的 part_ids 列表会直接被拦,不用自己写一堆 if;
- 异步接口支持,配合 asyncpg 查数据库,高并发下单场景不至于太难看;
- 自动生成 Swagger 文档,联调的时候前端同学直接访问 /docs 就能试接口。
有人可能觉得 FastAPI 在国内生态不如 Django 好,但纯 API 项目真的不需要 Django 那么重的全家桶。我在项目里只用到 FastAPI + SQLAlchemy + Pydantic,路由自动前缀统一挂在 /api 下,错误码统一返回{"code": 400, "message": "xxx"}结构,后续接 H5 端、管理后台接口也完全兼容。
2.2 uni-app 解决多端复用问题
前端选 uni-app 的理由更直接:微信小程序、H5、安卓 App 三端同一套代码。uni-app 底层虽然还是编译到各端,但 Vue 3 的语法和生命周期在小程序端是完整保留的,开发体验比直接用原生小程序写舒服很多。尤其是这个项目未来可能有商家端 App 的需求,用 uni-app 走一遍就能省掉一个独立团队。
创建项目的时候我特意选了 Vue 3 + TypeScript 模板。TypeScript 在配件规格对象这种结构不固定的场景下非常有用,能给 specs 定义一个联合类型,写代码的时候字段提示不会丢。
当然,选择 uni-app 也有代价:官方组件体系跟微信原生组件不完全一致,一些高级能力(比如虚拟列表)要等编译结果出来在开发者工具里实测,不能只看编译产物。这个放到后面踩坑的部分细说。
2.3 工程目录:前后端分离的仓库长什么样
操作层面,我是两个仓库并行维护的,前端 uni-app 工程由 HBuilderX 或 CLI 创建,后端是独立的 Python 工程。uni-app 部分我的目录是这样组织的:
uniapp-mall/ ├── src/ │ ├── pages/ # 页面 │ │ ├── home/index.vue # 首页 │ │ ├── parts/list.vue # 配件列表 │ │ ├── parts/detail.vue # 配件详情 │ │ ├── configurator/index.vue # 组装机配置器 │ │ ├── cart/index.vue # 购物车 │ │ ├── order/confirm.vue # 确认订单 │ │ ├── order/list.vue # 订单列表 │ │ └── user/index.vue # 我的 │ ├── api/ # 接口封装 │ ├── components/ # 公共组件 │ ├── stores/ # Pinia 状态管理 │ ├── utils/request.ts # 请求封装 │ ├── App.vue │ ├── main.ts │ ├── manifest.json # 微信 AppID 等配置 │ ├── pages.json # 页面路由与导航栏配置 │ └── uni.scss └── package.json后端 Python 部分:
python-server/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── routers/ │ │ ├── auth.py # 微信登录 │ │ ├── parts.py # 配件列表/详情 │ │ ├── config.py # 组装机配置校验 │ │ ├── cart.py │ │ └── order.py │ ├── models/ # SQLAlchemy 模型 │ ├── schemas/ # Pydantic 请求/响应模型 │ ├── services/ │ │ ├── compatibility.py # 兼容性校验引擎 │ │ └── wechat.py # 微信接口封装 │ └── core/ # 配置、数据库连接 └── requirements.txt后端跑起来以后,用 uvicorn 启动,本地开发uvicorn app.main:app --reload --port 8000,小程序端在开发者工具里把"不校验合法域名"勾上,就能直接连本地调试。这一步对 uni-app 项目是通用的,不需要额外配置代理。
3. 配件数据建模:零配件怎么存才不会乱
3.1 一张统一的配件表 + 规格 JSON
配件数据建模是这类项目最容易翻车的地方。八类配件的规格差异极大:CPU 有插槽、核心数、TDP;显卡有长度、功耗、显存;机箱有板型支持、显卡限长、散热限高。如果给每个品类建一张表,字段会膨胀到不可维护;如果全塞进一张"万能表",又会因为字段互相冲突失去意义。
我最终的方案是:一张统一的 parts 表,存储所有通用字段,再用一个 JSON 字段 specs 存储品类差异化规格。
CREATE TABLE part ( id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT NOT NULL, -- cpu / motherboard / gpu / memory / storage / psu / case / cooler name TEXT NOT NULL, brand TEXT, model TEXT, price NUMERIC NOT NULL DEFAULT 0, stock INTEGER NOT NULL DEFAULT 0, cover TEXT, -- 封面图 URL specs TEXT NOT NULL DEFAULT '{}', -- 差异化的规格 JSON is_on_sale INTEGER NOT NULL DEFAULT 1, created_at TEXT DEFAULT (datetime('now')) );这是一个先牺牲一点查询性能、换开发效率的方案。specs 字段里存什么、校验的时候怎么取值,在 Python 的 Pydantic 模型里定义清楚,每个品类一组字段。
比如 CPU 的 specs:
{ "socket": "LGA1700", "tdp": 125, "cores": 16, "threads": 24, "base_clock": "3.4GHz", "integrated_gpu": true }显卡的 specs:
{ "length_mm": 337, "power_w": 320, "recommended_psu_w": 750, "vram": 12, "interface": "PCIe 4.0 x16" }用 JSON 而不是拆成多张表的核心原因是"写代码时心智负担小",新增一个品类只需要扩展 schemas,不需要动数据库表结构。上线以后要加水冷散热器,加的只是 specs 的枚举值,这是我认为最适合中小型商城项目的数据组织方式。
3.2 兼容性字段:把八类配件统一到"可计算"的维度
数据建模不只是把字段存下来,更重要的是让这些字段能被兼容性引擎计算。我从八类配件里提炼出一组"兼容维度",作为 specs 中必填的公共约定:
| 品类 | 关键兼容字段 | 作用 |
|---|---|---|
| CPU | socket, tdp | 匹配主板插槽、参与功耗估算 |
| 主板 | socket, form_factor, ram_type | 匹配 CPU、内存;机箱板型支持 |
| 内存 | ram_type, capacity, speed | 匹配主板代数,提示是否超频 |
| 显卡 | length_mm, power_w, recommended_psu_w | 匹配机箱显卡限长、电源功率 |
| 电源 | wattage, psu_form_factor | 匹配机箱电源位、整机功耗 |
| 机箱 | support_form_factors, max_gpu_length, cooler_height | 板型、显卡限长、散热限高 |
| 散热器 | socket_list, height_mm, radiator_size | 匹配 CPU 插槽、机箱限高 |
| 硬盘 | interface, form_factor(2.5/3.5/M.2) | 匹配主板接口,预留校验规则 |
这些字段不是拍脑袋定的,而是对照真实装机经验整理出来的。以机箱为例,support_form_factors 用数组存储,比如["ATX", "mATX", "ITX"],这样中塔机箱可以兼容多种主板,校验规则里只要有交集就算通过。
3.3 推荐配置单:给配置器提供起点
配置器如果每次都是空白的八个占位符,小白用户会很茫然。所以我在数据层预置了三档推荐配置单:3000 元办公机、6000 元网游机、10000 元生产力机。每张配置单本质上就是一个"预设的 part_ids 数组",前端进入配置器时,如果没选过任何配件,默认加载推荐配置单并触发一次完整校验。
这个设计对后续运营也很友好:电商活动期间想推某个型号的 CPU,只需要改推荐配置单,不需要改前端代码。同时,推荐配置单的存在也降低了用户的决策成本,配置器的主页不是一堆"待选择",而是一套完整的、马上能下单的方案,用户改任意一件,系统重新校验并报价。
4. 兼容性校验引擎:组装机配置的灵魂
4.1 五条核心校验规则的拆解
兼容性校验是整个项目最不能糊弄的部分。规则设计得不好,用户要么被错误拦截,要么被骗着下单然后又退货。我把校验拆成了 error 和 warning 两级:error 表示绝对不能装,warning 表示能装但不建议,比如电源余量不足就是 warning 而不是拦截。
规则一:CPU 插槽与主板插槽必须一致。Intel 的 LGA1700 不能插在 AM4 主板上,这是最常见的死局,必须 error。
规则二:内存代数必须和主板支持一致。DDR5 内存插不进 DDR4 插槽,这个也是 error。
规则三:主板板型必须能被机箱支持。支撑的逻辑是判断主板 form_factor 是否在机箱 support_form_factors 列表里,只要有一个交集就放行。比如 mATX 主板放进支持 ATX 和 mATX 的中塔机箱,完全没问题。
规则四:显卡长度不能超过机箱限长。显卡长度在 specs 里有 length_mm,机箱限长是 max_gpu_length,超了就 error。这个坑非常隐蔽,很多玩家买显卡的时候只盯着性能,完全没想过机箱塞不塞得下。
规则五:散热器必须支持当前 CPU 插槽,高度不能超过机箱限高。这里风冷和水冷要分开判断:风冷看 socket_list 和 height_mm,水冷看散热器尺寸,比如 240 冷排是否被机箱支持。简化处理时我只校验了 socket 和 height,水冷机箱兼容性放在 warning 级别,避免规则太严格把一些特殊玩法挡在门外。
4.2 校验接口的实现与返回结构
校验逻辑放在后端的好处是规则更新不用发小程序版本。前端每改一个配件,把当前八个部位的 part_ids 打包 POST 到/api/config/check,后端返回完整校验报告。
核心引擎代码简化后长这样:
class CompatibilityEngine: def __init__(self, parts: dict): self.parts = parts # key: category, value: part dict self.issues = [] def check(self): self.check_cpu_motherboard() self.check_ram_motherboard() self.check_motherboard_case() self.check_gpu_case() self.check_cooler() self.check_power() return { "compatible": not any(i["level"] == "error" for i in self.issues), "issues": self.issues, "total_price": self.total_price(), "total_power": self.total_power(), }单个规则的示例:
def check_cpu_motherboard(self): cpu = self.parts.get("cpu") mb = self.parts.get("motherboard") if not cpu or not mb: return cpu_socket = cpu["specs"].get("socket") mb_socket = mb["specs"].get("socket") if cpu_socket != mb_socket: self.issues.append({ "level": "error", "message": f"CPU 插槽 {cpu_socket} 与主板插槽 {mb_socket} 不匹配", })FastAPI 接口层只做参数校验和数据装配,真正的业务逻辑收敛在 engine 里:
@app.post("/api/config/check") async def check_config(req: ConfigCheckRequest): parts = await get_parts_by_ids(req.part_ids) part_map = {p.category: p for p in parts} engine = CompatibilityEngine(part_map) return engine.check()这样做的好处是接口特别薄,以后如果要支持 H5 端或者 App 端,复用同一个 service 即可。前端拿到报告以后把 issues 列表渲染在配置器底部,error 红色标出,warning 黄色提示,用户改到兼容之后实时刷新。
4.3 电源功率估算:别让用户配出"开机重启机"
电源校验其实分两层:一是电源是否放得进机箱,二是瓦数够不够。瓦数估算我采用"累加 TDP + 余量系数"的方式,这也是很多 DIY 玩家手算的简化版:
- 整机估算功耗 = CPU TDP + 显卡实际功耗 + 固定 50W(主板、风扇、内存、硬盘)
- 建议电源瓦数 = 估算功耗 × 1.2 到 1.5 之间取整
比如 CPU 125W、显卡 320W,估算功耗就是 125 + 320 + 50 = 495W,乘以 1.2 得到 594W,那么建议至少上 650W 电源。如果用户选了 550W 电源,就给出 warning:不是不能用,但长期满载不稳定,而且未来想升级显卡就没有余量了。
这里值得强调的是,recommended_psu_w这个字段是厂商写进显卡规格的推荐值,它通常比单纯按 TDP 算出来的结果更保守,所以我的规则是两者取较大值来判断:
def check_power(self): est = self.total_power() psu = self.parts.get("psu") if not psu: return wattage = psu["specs"].get("wattage", 0) suggested = max(est * 1.2, self.parts["gpu"]["specs"].get("recommended_psu_w", 0)) if wattage < suggested: self.issues.append({ "level": "warning", "message": f"电源额定 {wattage}W 偏低,建议至少 {int(suggested)}W" })总功耗计算也要在返回结构里带上,配置器界面底部直接展示"整机满载功耗约 xxx W",让用户对这套配置的定位有个直观感受。
5. uni-app 端配置器与商城页面的落地细节
5.1 页面路由与导航栏配置
uni-app 的小程序端页面注册在 pages.json 里,导航栏标题、下拉刷新这些都能直接配置,不用写原生页面代码。我这个项目的页面清单如下:
{ "pages": [ { "path": "pages/home/index", "style": { "navigationBarTitleText": "首页" } }, { "path": "pages/parts/list", "style": { "navigationBarTitleText": "配件列表" } }, { "path": "pages/parts/detail", "style": { "navigationBarTitleText": "配件详情" } }, { "path": "pages/configurator/index", "style": { "navigationBarTitleText": "组装机配置" } }, { "path": "pages/cart/index", "style": { "navigationBarTitleText": "购物车" } }, { "path": "pages/order/confirm", "style": { "navigationBarTitleText": "确认订单" } }, { "path": "pages/order/list", "style": { "navigationBarTitleText": "我的订单" } }, { "path": "pages/user/index", "style": { "navigationBarTitleText": "我的" } } ], "globalStyle": { "navigationBarTextStyle": "black", "navigationBarBackgroundColor": "#FFFFFF", "backgroundColor": "#F5F6F8" } }这里提醒一个细节:如果配置器页面想用自定义导航栏来显示实时总价,需要在页面 style 里设置"navigationStyle": "custom",然后自己用uni.getSystemInfoSync()获取状态栏高度,把页面内的自定义头部往下顶。这个坑在 uni-app 微信小程序端尤其明显——顶部导航栏高度在不同机型上不一致,直接用官方默认导航栏反而最省事。
5.2 配置器的主页面状态管理
配置器页面是核心交互区,我把八个部位的选中状态放进 reactive 对象里,每个部位对应一个配件对象或 null:
const selectedParts = reactive<Record<PartCategory, PartItem | null>>({ cpu: null, motherboard: null, memory: null, gpu: null, storage: null, psu: null, case: null, cooler: null }); const report = reactive<CheckReport>({ compatible: true, issues: [], totalPrice: 0, totalPower: 0 });页面上渲染一个"部位清单",每个部位一行,左侧是品类名,右侧是已选配件名或者"去选择"按钮:
<view class="part-row" v-for="(cat, i) in partCategories" :key="cat"> <text class="part-cat">{{ catName[cat] }}</text> <view class="part-value" @tap="openPicker(cat)"> <text v-if="selectedParts[cat]">{{ selectedParts[cat].name }}</text> <text v-else class="placeholder">去选择</text> </view> </view>点击"去选择"后不是跳页面,而是打开一个底部弹层,弹层里请求/api/parts?category=xxx获取该品类配件列表,支持搜索和按价格排序。选择后把结果赋给 selectedParts,马上调用checkConfig()。
核心的校验调用逻辑:
async function checkConfig() { const partIds = partCategories .map((cat) => selectedParts[cat]?.id) .filter(Boolean); const res = await checkBuild({ part_ids: partIds }); report.compatible = res.compatible; report.issues = res.issues; report.totalPrice = res.total_price; report.totalPower = res.total_power; }页面底部固定一个"总价 + 整机功耗 + 加入购物车"的操作栏。总价实时刷新、兼容性 error 存在时禁用下单按钮。这一步看起来简单,但对体验影响极大:用户每换一个配件,反馈必须在一秒内出现,否则就会觉得自己在做选择题而不是在买电脑。
5.3 请求封装与登录态的公共处理
小程序端的接口请求我用一个 Promise 包装过的 request 函数统一管理,utils/request.ts:
const BASE_URL = 'https://api.example.com'; export function request<T>(options: { url: string; method?: 'GET' | 'POST'; data?: any; }): Promise<T> { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', Authorization: `Bearer ${uni.getStorageSync('token') || ''}` }, success: (res) => { const body = res.data as ApiResponse<T>; if (body.code === 0) { resolve(body.data); } else { uni.showToast({ title: body.message, icon: 'none' }); reject(body); } }, fail: (err) => reject(err) }); }); }登录态的处理逻辑是:首次进入小程序,uni.login()拿到 code,后端用 code 换 openid 后再签发自己系统的 token,前端把 token 存到uni.setStorageSync。每次请求在 header 里带 token。这样小程序退出重进之后,token 还留在本地,不需要每次都重新登录。
配件列表页我用了分页加载,每次请求 20 条,滚动到底部自动加载下一页。这里的参数是 page 和 page_size,响应结构统一为{ list, total, page },这样首页推荐位、配置器弹层、列表页可以共用同一组接口。
6. 微信小程序登录、支付与上线:从能跑到能上线
6.1 code 换 openid 的登录流程
微信小程序不能像传统网站那样用账号密码登录,唯一可信的标识是微信的 openid。流程是前端先用uni.login()拿到临时 code,传给后端,后端用 code 去微信接口换 openid,再拿 openid 去自己数据库里找对应用户,没有就注册一个,最后签发自己的 token。
后端关键代码:
import requests WX_APPID = "你的AppID" WX_SECRET = "你的AppSecret" def wx_code2session(code: str) -> dict: resp = requests.get( "https://api.weixin.qq.com/sns/jscode2session", params={ "appid": WX_APPID, "secret": WX_SECRET, "js_code": code, "grant_type": "authorization_code", }, timeout=5, ) data = resp.json() if "openid" not in data: raise ValueError(f"微信登录失败: {data}") return data注意,code2session 的 session_key 是微信用来解密手机号等敏感信息的密钥,不是用来维持登录态的。正确的做法是后端把 openid 映射到自己的用户表,生成 JWT token 返回给前端。token 过期时间我设的是 7 天,配合小程序长期不活跃重新登录的逻辑就够用了。
获取手机号是另一个独立能力,需要通过<button open-type="getPhoneNumber">触发,获取的 code 传给后端后再调微信接口解密。这里有个很重要的限制条件:小程序必须是已认证的非个人主体,个人主体小程序拿不到这个权限。项目如果只是自用演示,可以先不做手机号绑定,用 openid 做用户标识足够。
6.2 微信支付接入与回调
支付是这类商城小程序的标配。微信支付的流程是:后端统一下单生成支付参数,前端调uni.requestPayment拉起收银台,用户付款后微信服务器往你的回调地址推送支付结果。
统一下单核心参数(JSAPI 支付):
- appid:小程序 AppID;
- mchid:商户号;
- description:商品描述;
- out_trade_no:商户订单号;
- notify_url:支付结果回调地址;
- amount:金额,单位是分。
后端收到回调以后必须做验签和解密,然后更新订单状态。这个环节最容易出的问题是回调接口没返回微信要求的应答格式,导致微信反复重试。回调处理完以后要返回 200 和固定格式的成功报文,哪怕业务逻辑处理失败了也要先把报文收到,避免重复通知堆积。
uni-app 端的调用:
const payment = await request({ url: '/api/pay/create', method: 'POST', data: { orderId: orderId } }); uni.requestPayment({ provider: 'wxpay', timeStamp: payment.timeStamp, nonceStr: payment.nonceStr, package: payment.packageValue, signType: 'RSA', paySign: payment.paySign, success: async () => { await confirmOrder(orderId); uni.redirectTo({ url: '/pages/order/list' }); } });这里务必注意:支付参数必须由后端生成,前端永远不要自己拼签名。我见过有人图省事把商户密钥配到小程序端,上线没几天账户就被刷了——这条底线绝不能碰。
6.3 域名、认证与审核:上线前最后也是最磨人的一关
小程序上线前的三大坎:认证、合法域名、类目资质。
认证方面,微信小程序目前个人主体认证免 300 元,但功能有诸多限制;企业主体认证 300 元/年,才能开通微信支付、手机号获取、附近的小程序等能力。这个成本在立项预算里就要算进去,不要等到功能做完了才发现个人主体不能接支付。
合法域名方面,所有uni.request的接口域名必须是 HTTPS,并且在小程序后台配置为"request 合法域名"。域名要求已经备案的国内域名加 SSL 证书。本地开发时可以在微信开发者工具勾选"不校验合法域名",但真机预览和线上版本必须走正规域名。上线前建议把 API 的 baseURL 从配置文件里抽出来,测试环境和生产环境通过环境变量切换。
审核方面,电商类小程序需要选择正确的服务类目,比如"电商平台"或"商家自营",并且可能要求提供营业执照、ICP 备案等资质。电脑配件属于 3C 数码类目,类目资质审核比较严格。另外,涉及在线支付还需要声明"虚拟支付"或"实物商品"业务场景。这块我吃过亏——第一次提交审核因为类目选错被驳回,改完类目又重新走了两天的审核排队。
6.4 一路踩下来的坑,挑三个最值得说的
第一个坑是 uni-app 在小程序端有时 console.log 不打印。排查代码时一度以为逻辑没执行,后来才发现是开发者工具的控制台过滤级别问题,或者因为日志量太大被工具丢弃。遇到这种情况,我一般优先用console.info打关键断点,或者直接在界面上临时渲染一个调试文本,比在控制台翻半天高效得多。
第二个坑是微信小程序包体大小限制。小程序主包不能超过 2MB,否则无法上传。项目里配件图片如果全部放本地,包体直接爆炸,所以设计之初就要决定:所有商品图放 CDN,本地只放默认占位图和少量 icon。图片路径在数据库里存完整 URL,开发环境可以用自己的测试图床,生产环境必须换正式 CDN 并配置好防盗链。
第三个坑是配置器里购物车的数据结构。一个配置单包含八个配件,如果按普通商品购物车那样一行一个商品存,下单时很难聚合。我把购物车条目设计成"配置单"和"单品"两种类型:配置单是一个 JSON 数组存储整套 part_ids,下单时按配置单整体校验和扣库存。这样既支持"整套方案直接下单",也保留了"单独买个散热器"的灵活性。
这个项目从立项到跑通全流程,前后花了一个多月,大部分时间不是花在写代码上,而是花在"规则定义"和"边界情况"上。比如不同品牌电源的瓦数标注方式不一样,有些是峰值,有些是额定;机箱参数页写的显卡限长有的含线材余量、有的不含。程序员不能替用户做决定,我们能做的就是把规则和提示写得尽量清楚,把 error 与 warning 分开,让用户理解"为什么不行"以及"怎么改就行"。
最后分享一个亲测有效的经验:兼容性校验规则先用"推荐配置单"做单元测试数据源。我手写了二十多套真实配置,从 2000 元亮机卡配置到 15000 元 4090 顶配,每套都用手工判断一遍兼容性,再拿引擎跑一遍,两边结果对不上的地方,基本就是规则该修的 bug。把这个步骤纳入发布流程之后,配置器上线以后几乎没有用户反馈过"能装却不能下单"的错判。如果你也在做这类带规则引擎的项目,这个笨办法值得一试。