ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+微信小程序打造电子元器件商城:从SKU设计到支付落地

SpringBoot+微信小程序打造电子元器件商城:从SKU设计到支付落地 做电子元器件商城跟做普通电商还真不太一样。我第一次接到这个项目时以为就是套个现成的商城模板改改商品字段结果越做越发现这玩意儿的水比想象中深——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好上手缓存RedisSpring Data RedisToken、验证码、首页热点数据任务调度Spring Scheduled超时订单取消、定时释放库存中小量级完全够用接口文档Knife4jOpenAPI3前后端联调神器自动生成接口文档小程序端原生 Vant组件库原生调试最稳Vant省去大量UI造轮子时间数据库MySQL 8.x InnoDB事务支持完善供应JSON字段扩展参数技术选型没有标准答案但是每一个选择都要能够说出因为我遇到了什么需求所以我选了它。比如这里选Sa-Token而不是Spring Security就是因为我只需要一个小程序token登录搞一套完整的OAuth2授权流程完全是给自己加戏。2.2 数据库设计与核心表结构数据库设计是商城系统的地基地基如果歪了后面写多少业务代码都别扭。我用一个相对精简但完整的表结构方案来说明——这套设计在数据量百万级以内完全扛得住而且逻辑清晰。商品相关表拆成四张spuSPU主表id、spu_code、title、category_id、brand_id、main_image、imagesJSON数组、description、status、create_time等param_template参数模板表id、category_id、param_name、param_unit、is_sku_property、is_filterable。比如阻值是SKU属性、工作温度是普通描述属性product_skuSKU表id、spu_id、sku_code、spec_valueJSON格式如{resistance:10k,tolerance:1%,package:0603}、price、step_priceJSON数组如[{min:1,max:99,price:0.25},{min:100,max:999,price:0.18}]、stock、warning_stock、batch_no、statussku_stock_log库存流水表id、sku_id、record_type1占用、2扣减、3释放、4入库、quantity、order_no、operator、create_time订单核心四张表orders订单主表order_no、user_id、total_amount、freight_amount、discount_amount、pay_amount、pay_type、status0待付款、1已支付、2已发货、3已完成、4已取消、5已退款、consignee_infoJSON格式存地址和联系人、pay_time、deliver_time、remarkorder_item订单明细表id、order_no、spu_id、sku_id、sku_title、sku_spec、product_price下单时快照单价、quantity、total_price、imgcart购物车表为了性能考虑也可以直接存Redis但落库的好处是用户在多个设备上登录购物车数据还能同步我这里最终选了落库id、user_id、spu_id、sku_id、quantity、selected、create_timeuser_address地址表id、user_id、name、phone、province、city、district、detail、is_default用户相关表user用户主表id、openid唯一索引、nickname、avatar、phone、level1普通、2企业认证、status、register_timeuser_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统一返回体 ApiResultT │ ├── utilsJWT、MD5、Redis工具类 ├── config │ ├── RedisConfig、MybatisPlusConfig、Knife4jConfig │ └── WebMvcConfig拦截器注册、跨域处理 ├── controller │ ├── user用户、地址 │ ├── product商品、类目、品牌 │ ├── order下单、支付回调、退款 │ ├── cart购物车 │ └── admin后台管理接口 ├── service │ ├── mall前台业务服务 │ └── admin后台管理服务 ├── mapperMyBatis-Plus接口 ├── entity数据库实体 ├── dto入参对象分前台和后台 ├── vo出参对象按前端页面聚合 └── task定时任务超时关单、库存释放分层这里最容易被忽视的是DTO、VO和Entity的边界。很多新手直接把Entity丢给前端去解析JSON结果把数据库自增ID、逻辑删除位、内部备注全暴露了这不仅是安全问题还会导致接口字段一会儿多一会儿少前后端联调时整天吵架。我的习惯是Controller入口收DTOService内部转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 ApiResultLoginVO 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封装有10uF0805封装最小只有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. 读取购物车已选明细 ListCartItemVO cartItems cartService.listSelectedItems(userId); if (CollectionUtils.isEmpty(cartItems)) { throw new BizException(请先选择要结算的商品); } long totalAmount 0; ListOrderItem 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.port8080这样Nginx到应用这层是内网通信不经过防火墙也能连通。数据库分离部署。如果预算允许不要把MySQL放在应用服务器同台机器上至少要把备份目录独立出来。我这个项目初期为了省钱把它们放一起了结果某次磁盘耗尽导致数据差点丢后来赶紧买了块云盘做数据目录迁移再配了每天凌晨的自动备份。小程序的合法域名配置只有线上版本才需要开发调试时的不校验合法域名选项只是权宜之计千万别带着这个选项去提交审核。5. 常见问题与排查技巧实录这个部分是我最想写给后来人的因为里面每一个问题都是我一行行代码排查过来的。5.1 小程序端高频问题先说一个最常见的iOS下输入框聚焦时页面被顶起。在手机上输入收货地址时键盘弹起会把页面整个顶上去松开键盘后页面回不到原位看起来像卡住了。解决办法是在输入框的blur事件里调用wx.pageScrollTo({scrollTop: 0})或者使用cursor-spacing属性给输入框留出空间。第二个高频问题是分享卡片参数丢失。小程序商城做分享裂变的时候分享出去的卡片带了个path参数比如pages/product/index?skuId123从分享卡片进入小程序的用户在小程序里正常能拿到options.skuId但把它存在globalData里后下次冷启动时全局数据被清空商品页就打不开了。正确做法是把这个参数通过页面携带、或者存到本地缓存里而不是依赖globalData。第三个问题是真机预览和模拟器表现不一致。最常见的差异是wx.request在开发者工具里能正常请求真机上却直接进fail回调而且报错信息不明确。排查下来几乎都是没配置request合法域名导致的。开发阶段可以在工具详情里勾选不校验合法域名但这个问题一定要在提审前处理干净。5.2 后端高频问题后端最容易出问题是日期格式和时区。微信支付回调返回的时间戳是字符串格式SpringBoot默认用Jackson反序列化时如果你没做统一格式配置LocalDateTime直接解析报错或者相差8小时。我的处理是全局配置Jackson的JavaTimeModule和时区为GMT8同时在数据库连接串上必须加上serverTimezoneAsia/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?orderNoxxx这里只校验用户是否登录是不够的必须校验这个订单是否属于当前登录用户。我用的是在查询条件里强制拼接userId而不是查出来再判断这样更安全也不会漏掉权限校验场景。第三购物车和订单接口的并发防刷。恶意用户可能会同一时间疯狂点击提交订单造成库存锁竞争和订单表垃圾数据。我在提交订单的入口加了分布式锁锁粒度是用户维度理想情况下同一个用户的并发下单请求只能串行通过。6. 从零到上线的个人体会这个项目从动手到上线前后大概用了两个半月其中有将近一个月是在跟微信支付的调试死磕。回看整个过程最想给后来者分享的体会是做这种全栈项目真正花时间的不是写代码而是把业务逻辑的边界想清楚。比如阶梯价格到底以购物车为准还是下单为准这个问题如果不提前想透前后端很可能做出两种结果上线后用户投诉加购是一个价、结算变成另一个价追责的时候两边都有理但就是没人说得清最初的需求边界。再比如库存扣减的时机、超时订单的释放策略这类逻辑一旦上线再改不仅涉及存量订单的处理还可能让用户对平台信任度下降。如果你也想做类似的项目我可以给一个务实的行动路线第一周梳理业务流程画清楚状态机写功能清单不要纠结技术细节第二三周搭好数据库和SpringBoot骨架把商品、订单、用户三类主流程跑通第四五周小程序端把核心页面做出来前后端联调处理各种边界异常最后两周集中搞定支付回调、部署上线、真机测试、填完所有坑。过程中不要怕推翻重来。我第一次设计的购物车表结构里库存字段冗余了一份数据库库存导致预扣逻辑极其别扭后来干脆全部统一走Redis预扣 DB扣减的组合方案重构后代码量少了一半稳定性反而高了。有些东西不自己踩进去再爬出来书上讲一百遍你也不会有那个体感。最后再说一个小技巧上线前把小程序端每个按钮的所有可能的连续点击场景都过一遍。我在测试时发现提交订单按钮连续快速点十次会生成十个订单虽然后端有锁但用户手机上会显示十个待付款单。后来我在前端做了防重复提交的按钮禁用逻辑后端也在锁机制上做了完善。这种细节不会写在需求文档里但恰恰是决定用户体验和系统稳定性的关键。希望这篇文章能帮你绕开我踩过的大部分坑祝你的商城项目顺利上线、业务蒸蒸日上。
返回列表