☰
SpringBoot+微信小程序打造电子元器件商城:从SKU设计到支付落地
2026/10/5 3:49:18 网站建设 项目流程

做电子元器件商城,跟做普通电商还真不太一样。我第一次接到这个项目时,以为就是套个现成的商城模板改改商品字段,结果越做越发现,这玩意儿的水比想象中深——SKU粒度怎么拆、参数规格怎么存、库存批次怎么追溯、企业采购和个人用户的浏览习惯差异怎么处理,全是细节。这篇文章我会把我用SpringBoot和微信小程序从零搭建整套电子元器件商城系统的完整过程、踩过的坑、以及最终的代码结构方案都摊开讲,如果你也在做类似项目,或者正打算把某个垂直品类电商化,这篇可以帮你少走不少弯路。

1. 项目构思与需求拆解

1.1 电子元器件商城和普通电商的差异到底在哪

先说说这个项目为什么值得单独拿出来讲。电子元器件不同于衣服鞋子,它有几个显著特征决定了系统设计不能直接照搬通用商城逻辑。

第一是SKU结构复杂。一颗电阻,光参数就有阻值、精度、功率、封装、温度系数、材质类型,这还不算品牌、卷装还是散装、最小起订量。如果你把每个参数组合都当成一个独立SPU去维护,后台录入人员会疯掉,商品列表也会瞬间膨胀到几十万条。正确的思路是SPU(商品主体)+ 参数模板 + SKU(具体规格)三层结构,通过参数模板动态渲染规格选择器。

第二是价格体系多维。电子元器件价格经常跟采购量挂钩,同一个料号,买10个是一个价,买1000个又是另一个价。这就意味着价格字段不能做成单一数值,而是要设计成阶梯价格表,比如1-99片一档、100-999片一档、1000+一档,小程序端根据用户选择的购买数量动态展示对应单价。

第三是库存和批次敏感性。很多器件有批次和效期问题,尤其是芯片类,不同批次可能存在兼容性差异。商城系统如果只做简单的库存数字加减,后续售后和追溯会非常被动。设计时需要预留批号记录的能力,至少知道哪个订单消耗了哪个批次的货。

再说说这个项目的核心用户画像。电子元器件商城的使用者大体分两类:一类是电子工程专业的学生和DIY爱好者,他们通常单次采购量小、品类杂、对价格敏感;另一类是企业研发和采购人员,他们关注交期、库存真实性、批次信息,而且经常是批量询价。这两类人的使用习惯完全不同,小程序端在做入口设计时就得考虑分流——散户走快速下单,企业用户可能需要一个"询价单"的功能入口,即使系统初期不实现B端专属流程,UI和信息层级上也要提前预留空间。

1.2 功能清单与核心业务流程梳理

我不太建议一上来就画画改改,拿到需求第一件事是列功能清单,并且按优先级排序。这套系统我最终落地的时候,功能边界大概长这样:

用户端(微信小程序侧):

  • 微信授权登录,同时绑定手机号用于订单通知
  • 首页聚合类目入口、主推商品位、今日特价块
  • 商品搜索与筛选(按类目、按参数、按品牌、按价格区间)
  • 商品详情页(参数表格、SKU切换、阶梯价格联动、库存状态、加入购物车)
  • 购物车(条码式并列展示,支持修改数量、批量删除、已选金额汇总)
  • 订单确认页(收货地址管理、配送方式、买多件时阶梯价计算)
  • 订单中心(待付款、待发货、待收货、已完成、售后记录)
  • 个人中心(积分、优惠券、足迹、地址夹、设置)

管理后台(SpringBoot + Web管理界面):

  • 商品管理(SPU/规格参数/SKU/阶梯价/库存)
  • 类目与品牌管理
  • 订单管理(发货、取消、退款审核)
  • 用户管理(等级、状态、行为日志)
  • 优惠券和营销规则配置
  • 数据看板(日活、GMV、商品Top N)

整个系统的核心业务链路是:用户浏览商品 → 加入购物车 → 提交订单 → 微信支付 → 商户发货 → 用户确认收货。这个链路看起来跟普通电商一致,但中间有两个关键分支需要重点设计:

一个是阶梯价格的计算时机。购物车阶段是预估,提交订单时是锁定,到了支付回调后是最终确认,如果商品价格在这几个节点之间发生了改动,必须有一套机制保证不引起纠纷。我的方案是在提交订单的接口里把当时的商品单价、阶梯档位、应付总价全部快照进订单明细表,后续只以快照值为准,后台怎么改价都不影响已生成的订单。

另一个是库存扣减的时机。电子元器件商城最忌超卖。我最终采用了下单预占用库存、支付成功转正式扣减、超时未支付自动释放的机制。这套逻辑看起来繁琐,但对控制超卖和恶意占单非常有效,尤其适合库存数量少但单价高的电子元器件品类。

2. 技术选型与整体架构

2.1 为什么是SpringBoot + 微信小程序

技术栈的选择往往不是追求最炫,而是追求团队上手最快、资料最多、坑最少能填平。微信小程序作为C端载体,理由再直白不过:流量入口现成、微信支付链路短、用户无需下载App、分享裂变方便、开发调试门槛也比原生Android/iOS低得多。尤其在高校毕业设计和中小型团队创业项目里,小程序是投入产出比最高的选择,不需要考虑应用商店审核,开发完直接上传体验版,给客户和老师演示也方便。

后端选SpringBoot的理由同样很实在。电商系统的核心是事务、数据一致性、权限控制,Spring生态在这方面的积累非常成熟。Spring Boot 2.x内嵌Tomcat,打包即JAR,部署的时候一条java -jar命令搞定,不需要单独装容器。更关键的是它的生态整合能力——集成Redis做缓存储存验证码和购物车临时数据,集成MyBatis-Plus处理复杂的商品参数查询,集成Spring Security或Sa-Token做接口鉴权,集成XXL-Job或Spring自带的定时任务处理超时订单,每一步都有现成的轮子能抄。

我见过有人用Python Flask或者Node.js快速搭后台的,不是说不行,而是当项目进入后期、开始处理秒级并发的库存扣减、分布式锁、消息队列补齐数据这类硬需求时,Spring Boot的成熟方案明显更省心。尤其这个项目还要对接微信支付和微信登录,官方SDK的Java版本更新及时,文档详细,遇到问题的排查成本也低。

为了把心得说透,我给一个我自己的选型建议表,大家可以直接参考:

关注维度选择方案理由说明
后端框架Spring Boot 2.7.x稳定版本,JDK8/11均兼容
ORMMyBatis-Plus单表CRUD零SQL,复杂查询用注解SQL
鉴权Sa-Token轻量,支持小程序token一体化,比Spring Security好上手
缓存Redis(Spring Data Redis)Token、验证码、首页热点数据
任务调度Spring @Scheduled超时订单取消、定时释放库存,中小量级完全够用
接口文档Knife4j(OpenAPI3)前后端联调神器,自动生成接口文档
小程序端原生 + Vant组件库原生调试最稳,Vant省去大量UI造轮子时间
数据库MySQL 8.x + InnoDB事务支持完善,供应JSON字段扩展参数

技术选型没有标准答案,但是每一个选择都要能够说出"因为我遇到了什么需求,所以我选了它"。比如这里选Sa-Token而不是Spring Security,就是因为我只需要一个小程序token登录,搞一套完整的OAuth2授权流程完全是给自己加戏。

2.2 数据库设计与核心表结构

数据库设计是商城系统的地基,地基如果歪了,后面写多少业务代码都别扭。我用一个相对精简但完整的表结构方案来说明——这套设计在数据量百万级以内完全扛得住,而且逻辑清晰。

商品相关表,拆成四张:

  • spu(SPU主表):id、spu_code、title、category_id、brand_id、main_image、images(JSON数组)、description、status、create_time等
  • param_template(参数模板表):id、category_id、param_name、param_unit、is_sku_property、is_filterable。比如"阻值"是SKU属性、"工作温度"是普通描述属性
  • product_sku(SKU表):id、spu_id、sku_code、spec_value(JSON格式,如{"resistance":"10k","tolerance":"1%","package":"0603"})、price、step_price(JSON数组,如[{"min":1,"max":99,"price":0.25},{"min":100,"max":999,"price":0.18}])、stock、warning_stock、batch_no、status
  • sku_stock_log(库存流水表):id、sku_id、record_type(1占用、2扣减、3释放、4入库)、quantity、order_no、operator、create_time

订单核心四张表:

  • orders(订单主表):order_no、user_id、total_amount、freight_amount、discount_amount、pay_amount、pay_type、status(0待付款、1已支付、2已发货、3已完成、4已取消、5已退款)、consignee_info(JSON格式存地址和联系人)、pay_time、deliver_time、remark
  • order_item(订单明细表):id、order_no、spu_id、sku_id、sku_title、sku_spec、product_price(下单时快照单价)、quantity、total_price、img
  • cart(购物车表),为了性能考虑也可以直接存Redis,但落库的好处是用户在多个设备上登录购物车数据还能同步,我这里最终选了落库:id、user_id、spu_id、sku_id、quantity、selected、create_time
  • user_address(地址表):id、user_id、name、phone、province、city、district、detail、is_default

用户相关表:

  • user(用户主表):id、openid(唯一索引)、nickname、avatar、phone、level(1普通、2企业认证)、status、register_time
  • user_level_rule(会员等级配置表):level、discount_rate、free_shipping_threshold等

数据库设计这里有一个关键决策:规格参数到底用JSON存还是用关联表存。我最终采用"基础字段 + JSON补充"的混合模式,也就是每个SKU的关键筛选参数单独建列,如resistance、capacity、voltage这几项高频筛选字段建立索引,其余冷门参数全部放JSON。这样既满足商品列表页的快速筛选,又不需要为几十个参数各建一张关联表,查询时的灵活度和开发效率都能兼顾。

2.3 后端目录结构与代码分层

一个清晰的目录结构是项目能不能长期维护的分水岭。我的SpringBoot项目采用经典的分层架构,但做了一些细节调整,更适合小程序商城这种业务场景:

com.example.elecmall ├── common │ ├── constant(统一常量) │ ├── exception(自定义异常 + 全局异常处理器) │ ├── result(统一返回体 ApiResult<T>) │ ├── utils(JWT、MD5、Redis工具类) ├── config │ ├── RedisConfig、MybatisPlusConfig、Knife4jConfig │ └── WebMvcConfig(拦截器注册、跨域处理) ├── controller │ ├── user(用户、地址) │ ├── product(商品、类目、品牌) │ ├── order(下单、支付回调、退款) │ ├── cart(购物车) │ └── admin(后台管理接口) ├── service │ ├── mall(前台业务服务) │ └── admin(后台管理服务) ├── mapper(MyBatis-Plus接口) ├── entity(数据库实体) ├── dto(入参对象,分前台和后台) ├── vo(出参对象,按前端页面聚合) └── task(定时任务:超时关单、库存释放)

分层这里最容易被忽视的是DTO、VO和Entity的边界。很多新手直接把Entity丢给前端去解析JSON,结果把数据库自增ID、逻辑删除位、内部备注全暴露了,这不仅是安全问题,还会导致接口字段一会儿多一会儿少,前后端联调时整天吵架。我的习惯是Controller入口收DTO,Service内部转Entity,输出给前端的一律用VO组装,宁可多写几个转换方法,也不图省事直接透传。

3. 核心模块实现细节

3.1 微信小程序登录与用户体系设计

小程序登录是整个系统的第一道门,它的流程跟传统的账号密码登录完全不同。wx.login() 拿到的 code 是一次性的,有效期五分钟,需要在后端用这个 code 换取 openid 和 session_key。这个openid就是用户在微信生态里的唯一身份标识,我拿它作为用户表的外键。

几个关键点必须说清楚:

第一,code换session的过程必须放在后端做。有的同学图省事在前端直接请求微信接口,把appsecret暴露在小程序代码包里,这简直是把家门钥匙放在门口的脚垫底下。正确的姿势是前端把code传给后端,后端用appid + appsecret + code去换取 openid,整个过程中appsecret永远保存在服务端。

第二,用户信息不要一次性全部强求。第一次登录只需要openid就够了,后面的昵称头像手机号,可以在用户主动触发某个功能时渐进式补充,比如要下单了再绑定手机号,要展示个人中心了再更新头像和昵称。这种渐进式授权对转化率更友好,如果一进来就弹窗让用户填一堆资料,弃用率会高得惊人。

第三,登录之后签发的token要设计一个合理的有效期。我使用的是Sa-Token的token机制,默认30天过期,每次请求自动续期。对于商城类应用来说,这种"长会话轻鉴权"的策略比动不动就让用户重新登录更实用。用户关掉小程序两个月后打开,发现购物车和身份信息都还在,体验就好得多。

代码层面,wx.login() 的处理核心是这样的:

@PostMapping("/login") public ApiResult<LoginVO> login(@RequestBody @Valid WxLoginDTO dto) { // 1. 微信官方接口,用code换openid和session_key WxSessionResult session = wxService.code2Session(dto.getCode()); if (session == null || session.getOpenid() == null) { throw new BizException("微信登录凭证已过期,请重试"); } // 2. 从库里查这个openid对应的用户 User user = userMapper.selectOne(wrapper.eq("openid", session.getOpenid())); if (user == null) { // 3. 新用户自动注册,分配默认等级 user = User.builder().openid(session.getOpenid()) .nickname("微信用户") .level(1).status(1).build(); userMapper.insert(user); } // 4. 签发token并返回用户基础信息 String token = StpUtil.login(user.getId()); return ApiResult.ok(new LoginVO(token, user)); }

这里补充一个容易踩的坑:openid换session时,微信官方接口返回的session_key涉及敏感数据解密,绝对不能存到数据库方便后续调用。因为小程序端通过wx.getUserProfile或者手机号快速验证组件拿到的加密数据,需要用这个session_key做解密。如果确实需要存,也要放在Redis里且设置短时过期,用完立即删,防止用户身份被伪造。

3.2 商品SKU规格组合与阶梯价格设计

商品模块是整个系统最核心的部分。电子元器件的SKU组合特性,让我在设计时把规格和价格拆成了两层,并且每层都有独立的逻辑。

规格组合的设计思路是"参数模板驱动"。我在后台给每个类目绑定一个参数模板,模板里声明哪些参数参与SKU组合,哪些参数只是描述信息。比如贴片电阻类目,参与SKU组合的参数是阻值和封装、精度;而工作温度、环保标准这些只作为描述参数展示在详情页。前台小程序展示SKU切换时,就读取这类目模板,把所有可选值渲染成胶囊选项,用户每点一个选项,前端就拼出一个完整的specKey,去后端拉对应SKU的价格和库存。

规格到SKU的组合不是笛卡尔积无脑扩展的。比如某个型号的电容只有0603封装有10uF,0805封装最小只有1uF,如果系统自动把所有参数值组合全部生成一遍,就会产生大量不存在的无效SKU。我在这里的处理是,后台录入时允许选择"参数模板加载"或"手动指定规格值",能匹配时自动生成SKU,匹配不到允许人工录入。实际操作里,手动录入的比例其实非常高,因为电子元器件的现货库存和实际规格型号往往不是完整组合全覆盖的。

阶梯价格这一块,如果用关系型数据库的常规方式存多个价格字段,很快就会重构成一张完整的区间表。我的做法是在SKU表里加一个JSON字段step_price,结构长这样:

[ { "min": 1, "max": 99, "price": 0.25 }, { "min": 100, "max": 999, "price": 0.18 }, { "min": 1000, "max": 999999, "price": 0.12 } ]

后端提供一个统一方法,输入数量,遍历这个JSON数组,返回对应的档位价格和总价。这个查询涉及JSON解析,量大的时候性能会受影响,所以我在Service层加了Redis缓存,缓存键是sku_id:quantity,第一次计算后缓存一小时,热门商品命中率能到95%以上。

前端SKU选中的联动逻辑也非常考验细节。用户选了阻值,封装选项里要自动过滤掉和当前已选阻值无组合的规格。比如10k只和0603有组合,那封装胶囊里0402就应该置灰不可点。这个联动数据我是在详情接口里直接返回一个skuMatrix结构,把所有有效组合以JSON形式一次下发给前端,前端本地做状态判断,不需要每次点击都发起请求,体验很跟手,也减轻了后端压力。

3.3 购物车到订单提交的关键链路实现

购物车和订单链路最容易出的问题是价格不一致。我从一开始就坚持一个原则:购物车里的价格只是参考,一切以提交订单时的服务端实时计算为准。因此小程序的购物车页面即使自己算了总价,也不需要把总价传给后端。

购物车的CRUD本身没什么特别,真正核心的是从"提交订单"这个动作开始的事情。我贴一下提交订单接口的Service核心思路:

@Transactional(rollbackFor = Exception.class) public String submitOrder(SubmitOrderDTO dto) { // 1. 远程锁住用户,避免并发提交 String lockKey = "order:lock:" + userId; if (!redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) { throw new BizException("操作太频繁,请稍后再试"); } try { // 2. 读取购物车已选明细 List<CartItemVO> cartItems = cartService.listSelectedItems(userId); if (CollectionUtils.isEmpty(cartItems)) { throw new BizException("请先选择要结算的商品"); } long totalAmount = 0; List<OrderItem> orderItems = new ArrayList<>(); for (CartItemVO item : cartItems) { Sku sku = skuMapper.selectById(item.getSkuId()); // 3. 预扣库存:REDIS原子操作 decr Long remain = redisTemplate.opsForValue().decrement("stock:sku:" + sku.getId(), item.getQuantity()); if (remain == null || remain < 0) { // 回滚库存 redisTemplate.opsForValue().increment("stock:sku:" + sku.getId(), item.getQuantity()); throw new BizException("商品库存不足:" + item.getSkuTitle()); } // 4. 计算当前单价(阶梯价格) BigDecimal unitPrice = skuPriceCalculator.getPrice(sku.getStepPrice(), item.getQuantity()); OrderItem oi = OrderItem.builder() .productPrice(unitPrice) // 关键:快照 .quantity(item.getQuantity()) .totalPrice(unitPrice.multiply(item.getQuantity())) .build(); orderItems.add(oi); } // 5. 生成订单号,保存订单和明细 // 6. 清空购物车已选商品 // 7. 发送延迟消息,30分钟后未支付自动取消 orderMqSender.sendDelayCancel(order.getOrderNo(), 30 * 60 * 1000); return orderNo; } finally { redisLock.unlock(lockKey); } }

这套流程里有几个细节我要专门解释:

一是库存预扣时用了Redis的decrement原子操作,这一步是整个防超卖机制的基石。在并发量没有大到需要引入消息队列做最终一致性的阶段,Redis原子减一配合异常回滚已经足够可靠。数据库里真正扣减库存的SQL放在支付成功之后执行,这样即使用户下单后不付款,数据库里的库存也是满的,只占用Redis里的虚拟库存,超时后释放即可。

二是订单号必须自己生成,不要依赖数据库自增ID。电商场景订单号有个隐形需求——不能泄露当天订单量给竞争对手,所以要在订单号里嵌入随机数。我的生成策略是yyyyMMddHHmmss + 6位随机数 + 用户ID后4位,这样一个订单号既保证唯一、又能从订单号直接看出下单时间,排查问题非常方便。

三是超时未支付自动取消。我这里用Spring自带的延迟队列是做不到的,因为要支持30分钟后取消这种"准实时触发",所以引入了RocketMQ的延迟消息。如果项目不想引入MQ,退而求其次的方案是定时任务每30秒扫一次待付款订单,把超过30分钟的自动取消,这个方案在订单量一天几千笔的体量下完全够用,而且没有额外中间件,部署成本更低。

3.4 微信支付对接与退款处理

微信支付对接是整个项目里归属性最强、坑最多的部分,没有之一。小程序商城用的支付方式是JSAPI下单,流程是后端调用"统一下单"接口拿到prepayId,再用这个prepayId拼装支付参数返回给小程序端,小程序端用wx.requestPayment拉起收银台。

先说几个必踩的坑:

第一,支付金额的单位是分。前端传过来的金额可能是"0.25元",但是微信支付接口需要的参数是"25",也就是整数分。如果没有统一做金额单位转换,会出现用户看到的订单金额是100元,实际支付接口传的却是100分,支付完成后商家亏了99块钱。这种事千万别只在接口文档里说明,要在DTO上写校验注解,并且后端再做一次金额比对:拿订单号查库里的应付金额,和用户支付回调里的金额比对,分毫不差才算支付成功,否则标记为异常单等待人工处理。

第二,回调验签必须严格处理。微信支付回调通知会携带签名信息,必须用平台证书做验签,验签通过后才允许执行业务逻辑,否则任何人都可以伪造一个支付成功回调来白嫖你的商品。回调处理接口还有一个硬性要求:必须返回{"code":"SUCCESS"}给微信服务器,否则微信会连续重试通知48小时。处理完业务逻辑后,一定要先返回成功,再异步去响应用户。

第三,支付回调处理要做幂等。微信支付回调可能因为网络问题推送多次,所以你的支付成功处理逻辑必须天然幂等,最简单的方法是支付回调里根据订单号查状态,如果已更新为已支付就直接返回成功,不再重复执行业务。

退款接口设计时同样要谨慎。我的方案是后台退款必须填写退款原因和操作人,同时记录退款流水号。调用微信退款接口成功后,要把退款流水号关联到原订单上,这样后续对账的时候可以通过订单号查出来哪笔订单退了多少钱、退款流程走到哪一步。

4. 前后端联调与上线部署

4.1 小程序端页面架构与接口调用封装

小程序端我采用了原生开发方式,配合Vant Weapp组件库。在这个项目里,原生开发的调试便利性比使用跨端框架更稳定,尤其是涉及微信支付、微信登录这类官方能力时,原生API的兼容性永远是最靠谱的。

小程序端的代码结构也需要精心规划,不能所有页面都堆在pages目录下:

miniprogram/ ├── pages │ ├── index(首页) │ ├── category(分类页) │ ├── product(商品详情) │ ├── cart(购物车) │ ├── order(订单确认、订单列表、订单详情) │ └── user(个人中心) ├── components(自定义组件) ├── utils │ ├── request.js(封装wx.request) │ ├── auth.js(登录状态处理) │ ├── cart.js(本地购物车角标) │ └── price.js(金额格式化) ├── assets(静态图片、图标) └── app.js / app.json / app.wxss

接口请求封装这里,我建议所有请求都走一个统一的request.js方法,里面统一做三件事:带token、处理HTTP 401跳转登录、处理后端返回的业务错误码并调用wx.showToast提示。这样在业务代码里只需要关心请求成功后的数据渲染逻辑,错误处理全部收口在请求层。

分页加载这个点在电子元器件商品列表上尤其常见。器件类商品SKU多,一个列表可能几千条,如果一次性返回所有数据,小程序端渲染会非常卡。我用的方案是onReachBottom触发加载下一页,后端接口接收page和size参数,返回时带上hasMore标记,前端根据这个标记决定是否还能继续加载。这个模式在商品列表、搜索结果、订单列表、评论列表里通用,一次写好到处复用。

4.2 管理后台与接口鉴权方案

管理后台的构建我选择的是Vue3 + Element Plus + Vite这套组合。Vue3的响应式系统配合模板语法,页面开发效率非常高。管理后台的典型页面包括:商品列表(支持批量上下架、价格调整)、SKU管理(弹窗编辑规格、阶梯价)、订单管理(列表检索、发货操作)、数据统计(用ECharts画折线图展示销售趋势)。

管理后台和后端管理接口之间的鉴权,与小程序端的用户token鉴权是两套体系。管理端走的是"用户名+密码"登录,登录成功签发一个独立的adminToken,后端通过拦截器校验这个token的同时,还要校验操作人的角色权限。比如普通运营只能改商品信息和价格,不能动退款审核;财务角色才能操作退款和查看收入明细。

角色权限这块我用的是Sa-Token的注解鉴权能力,在Controller方法上打上@SaCheckPermission("order:refund")这样的标签,框架自动拦截无权限请求。这样权限控制变得很薄很清晰,而且新建角色只需要调整数据库里的权限配置,不需要改代码。

4.3 服务器部署与HTTPS证书配置

小程序的线上环境对通信有硬性要求:所有请求域名必须是HTTPS,并且要在小程序后台配置合法的request合法域名。所以部署微信小程序商城,你是绕不开域名、证书和云服务器这三件套的。

我的部署方案用一台2核4G的腾讯云/阿里云服务器,配置方式是:Nginx监听443端口,拦截HTTPS请求,反向代理到本机的SpringBoot应用(8080端口)。SSL证书用的是免费版的DV证书,阿里云和腾讯云都提供一年免费期,到期重新申请就行,没必要一开始就买几千块钱的企业证书。

部署的几个细节:

  • SpringBoot应用不要直接用默认端口启动。配合Nginx反代时,建议还是单独指定内部端口,例如server.port=8080,这样Nginx到应用这层是内网通信,不经过防火墙也能连通。
  • 数据库分离部署。如果预算允许,不要把MySQL放在应用服务器同台机器上,至少要把备份目录独立出来。我这个项目初期为了省钱把它们放一起了,结果某次磁盘耗尽导致数据差点丢,后来赶紧买了块云盘做数据目录迁移,再配了每天凌晨的自动备份。
  • 小程序的合法域名配置,只有线上版本才需要,开发调试时的"不校验合法域名"选项只是权宜之计,千万别带着这个选项去提交审核。

5. 常见问题与排查技巧实录

这个部分是我最想写给后来人的,因为里面每一个问题都是我一行行代码排查过来的。

5.1 小程序端高频问题

先说一个最常见的:iOS下输入框聚焦时页面被顶起。在手机上输入收货地址时,键盘弹起会把页面整个顶上去,松开键盘后页面回不到原位,看起来像"卡住了"。解决办法是在输入框的blur事件里调用wx.pageScrollTo({scrollTop: 0}),或者使用cursor-spacing属性给输入框留出空间。

第二个高频问题是分享卡片参数丢失。小程序商城做分享裂变的时候,分享出去的卡片带了个path参数,比如pages/product/index?skuId=123,从分享卡片进入小程序的用户,在小程序里正常能拿到options.skuId,但把它存在globalData里后,下次冷启动时全局数据被清空,商品页就打不开了。正确做法是把这个参数通过页面携带、或者存到本地缓存里,而不是依赖globalData。

第三个问题是真机预览和模拟器表现不一致。最常见的差异是wx.request在开发者工具里能正常请求,真机上却直接进fail回调,而且报错信息不明确。排查下来几乎都是没配置request合法域名导致的。开发阶段可以在工具详情里勾选"不校验合法域名",但这个问题一定要在提审前处理干净。

5.2 后端高频问题

后端最容易出问题是日期格式和时区。微信支付回调返回的时间戳是字符串格式,SpringBoot默认用Jackson反序列化时,如果你没做统一格式配置,LocalDateTime直接解析报错或者相差8小时。我的处理是全局配置Jackson的JavaTimeModule和时区为GMT+8,同时在数据库连接串上必须加上serverTimezone=Asia/Shanghai,否则MySQL连接可能直接拒绝。

第二个高频问题是MyBatis-Plus的乐观锁失效。用了@Version注解做乐观锁,但是每次update的时候如果不显式设置版本号字段,插件不会自动对version做自增操作,导致并发修改时数据被静默覆盖。这个问题的排查思路是打开MyBatis的SQL日志,看update语句里version是否带上了条件。

第三个问题我要重点说:跨域配置不是万能的。小程序端的请求没有浏览器同源策略,所以不需要考虑CORS,但管理后台是浏览器环境,必须配置好跨域。如果你发现小程序请求全通、后台请求却报403,十有八九是CORS配置只允许了部分来源,或者预检请求OPTIONS被拦截器拦掉了。我的解决方案是拦截器里放行OPTIONS请求,并且cors配置设置allowedOriginPatterns为 "*",然后凭token鉴权兜底。

5.3 性能优化和安全加固实践

商城这种读多写少的系统,性能瓶颈往往集中在商品列表和商品详情两个热点接口。我做的优化手段在文章前面提过一部分,这里完整列一下:

  • 首页、分类页、热销榜这些数据,定时预热到Redis,缓存10分钟,由后台商品上下架操作主动失效对应缓存。
  • 商品详情页,SPU信息、SKU列表、阶梯价、参数表格拆成多个缓存键,哪个数据变了就只刷哪块,避免一次大JSON全部重建。
  • 搜索接口用MySQL的LIKE '%keyword%'就能撑过几千SKU的量,但如果商品规模到几十万,就得考虑接入Elasticsearch。我建议前期不引入ES,别给自己加复杂度,真到量级再说。

安全加固这块,重点做三件事:

第一,防止接口被刷。小程序的code换token接口是最容易被刷的,因为code是一次性的,攻击者穷举不到,但可以直接刷你的登录接口打爆你的用户表。我的方案是给登录接口加一个简单的滑动窗口限流:同一个IP每分钟最多请求30次,超过就拒绝。

第二,校验入参是否有越权ID。比如GET /order/detail?orderNo=xxx,这里只校验用户是否登录是不够的,必须校验这个订单是否属于当前登录用户。我用的是在查询条件里强制拼接userId,而不是查出来再判断,这样更安全,也不会漏掉权限校验场景。

第三,购物车和订单接口的并发防刷。恶意用户可能会同一时间疯狂点击提交订单,造成库存锁竞争和订单表垃圾数据。我在提交订单的入口加了分布式锁,锁粒度是用户维度,理想情况下同一个用户的并发下单请求只能串行通过。

6. 从零到上线的个人体会

这个项目从动手到上线,前后大概用了两个半月,其中有将近一个月是在跟微信支付的调试死磕。回看整个过程,最想给后来者分享的体会是:做这种全栈项目,真正花时间的不是写代码,而是把业务逻辑的边界想清楚。

比如阶梯价格,到底以购物车为准还是下单为准,这个问题如果不提前想透,前后端很可能做出两种结果,上线后用户投诉"加购是一个价、结算变成另一个价",追责的时候两边都有理,但就是没人说得清最初的需求边界。再比如库存扣减的时机、超时订单的释放策略,这类逻辑一旦上线再改,不仅涉及存量订单的处理,还可能让用户对平台信任度下降。

如果你也想做类似的项目,我可以给一个务实的行动路线:第一周,梳理业务流程,画清楚状态机,写功能清单,不要纠结技术细节;第二三周,搭好数据库和SpringBoot骨架,把商品、订单、用户三类主流程跑通;第四五周,小程序端把核心页面做出来,前后端联调,处理各种边界异常;最后两周,集中搞定支付回调、部署上线、真机测试、填完所有坑。

过程中不要怕推翻重来。我第一次设计的购物车表结构里库存字段冗余了一份数据库库存,导致预扣逻辑极其别扭,后来干脆全部统一走Redis预扣 + DB扣减的组合方案,重构后代码量少了一半,稳定性反而高了。有些东西不自己踩进去再爬出来,书上讲一百遍你也不会有那个体感。

最后再说一个小技巧:上线前把小程序端每个按钮的所有可能的连续点击场景都过一遍。我在测试时发现,提交订单按钮连续快速点十次,会生成十个订单,虽然后端有锁,但用户手机上会显示十个待付款单。后来我在前端做了防重复提交的按钮禁用逻辑,后端也在锁机制上做了完善。这种细节不会写在需求文档里,但恰恰是决定用户体验和系统稳定性的关键。希望这篇文章能帮你绕开我踩过的大部分坑,祝你的商城项目顺利上线、业务蒸蒸日上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询