做小程序商城这个方向,我见过太多“买椟还珠”的项目。不少同学拿到一套源码,跑起来能看首页、能加购物车,就觉得完事了,结果答辩时被问一句“订单状态是怎么流转的”就卡壳。我整理的这套在线购物系统,更想强调的不只是“能跑”,而是整套项目如何从需求、表设计、接口、前后端联调一路走到最终交付。这篇文章我就把这个项目里真正有价值的零件拆开,讲清楚技术选型怎么定、核心模块怎么实现、文档怎么组织、调试时哪些坑最常见,给正在做课程设计、毕业设计或者打算接私活做小程序的你一份可以直接照抄的作业。
1. 项目全貌:这套商城系统的定位与技术选型
1.1 为什么选“原生微信小程序 + Spring Boot + MySQL”这套组合
技术选型这事儿,最忌讳跟风。我当时定方案之前,也纠结过要不要用 uni-app 一把梭,毕竟 HBuilderX 发行微信小程序的流程确实方便,一套代码还能再生出 App 端。但最终还是选了原生小程序加重构后端,原因很简单:这套系统的定位是教学演示和交付级 Demo,要的是“逻辑透明、文档齐全、调试容易”。
原生微信小程序的好处在于,整个生命周期、API 调用、组件层级都是微信官方的一套。出了问题去搜解决方案,资料最多、回复最快。比如“微信小程序顶部导航栏高度”这个问题,原生里用 wx.getMenuButtonBoundingClientRect() 拿胶囊位置,再结合系统状态栏高度就能精确算出导航栏真实高度,这些代码在原生环境里是稳定可复现的。换成 uni-app 虽然也能做,但中间多了一层框架的兼容和转换,对于新手来说,排查问题时就多了一道干扰。后端选 Spring Boot 则是因为它生态成熟,Idea 里启动、断点、热部署都很顺手,而且 Java 技术栈在大多数学校的课程设计、毕业设计里是“安全牌”,答辩时老师认可度高。MyBatis-Plus 负责减少重复的 CRUD 代码,Redis 做缓存和 token 存储,这套组合在中小型项目中已经被验证过无数次,属于典型的“稳妥方案”。
1.2 项目目录结构与工程组织方式
拿到这套系统源码,先别急着按启动按钮,建议先把目录结构过一遍。好的工程组织能帮你节省大量后期维护时间,这套项目的目录设计如下:
text复制shopping-mall-frontend/ # 微信小程序前端
pages/
index/ # 首页:商品列表、轮播图
category/ # 分类页:左侧分类、右侧商品
goods/ # 商品详情:SKU选择、加入购物车
cart/ # 购物车:增删改、结算入口
order/ # 订单:确认订单、订单列表、订单详情
user/ # 个人中心:登录、地址管理、我的订单
shopping-mall-backend/ # Spring Boot 后端
src/main/java/
controller/ # 接口层:接收参数、返回结果
service/ # 业务层:核心逻辑处理
mapper/ # 数据访问层:MyBatis-Plus
entity/ # 实体类
config/ # 全局配置:拦截器、跨域、swagger
src/main/resources/
application.yml # 数据库、Redis、端口配置
docs/ # 项目文档:需求、设计、接口、部署
前端每个页面文件夹里放 .wxml、.wxss、.js、.json 四个文件,这是小程序的标准结构。后端按经典的四层结构分包,清楚明了。docs 目录我习惯专门放需求文档、数据库设计文档、接口文档、部署文档,这些文档后面单独讲。这种组织方式最大的好处是,前后端完全分离,前端调试时只需要关注接口返回值,后端调试时只需要关注逻辑正确性,不会互相干扰。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心业务模块的设计与实现要点
2.1 登录鉴权这块,最容易被“跳过”的环节
很多同学做小程序商城,第一件事就是写商品列表接口,登录往后放。我的建议恰恰相反,先把登录打通,因为后续所有接口都依赖用户身份。小程序的登录流程和传统网页登录不太一样,它的核心是 wx.login() 拿到临时 code,然后后端用 code 去微信的 code2Session 接口换 openid 和 session_key。代码如下:
java复制// 小程序端:wx.login 获取临时code
wx.login({
success: (res) => {
wx.request({
url: baseUrl + '/api/user/login',
method: 'POST',
data: { code: res.code },
success: (res) => {
// res.data.token 为后端签发JWT
wx.setStorageSync('token', res.data.token);
}
});
}
});
后端拿到 code 后,调用微信接口换取 openid,然后查用户表,如果不存在就自动注册一个新用户,最后签发一个 JWT 返回给前端。这里有个关键点:JWT 里只放 userId 和 openid,不要放密码、手机号这类敏感信息。前端拿到 token 后存在 Storage 里,每个请求在 header 中携带 Authorization: Bearer <token>,后端用一个拦截器统一校验。
为什么不用传统的 Session?因为小程序端的请求环境天然适合无状态认证,Session 在移动端要处理 Cookie 的存取,而且如果后端将来做集群部署,Session 同步是个大麻烦。JWT 把用户信息加密放进 token 本身,后端只要验签就能确认身份,扩展性更强。当然 JWT 也有被泄露的风险,所以我会给它设置过期时间,比如 7 天,前端每次请求时判断 token 是否快过期,提前调用刷新接口重新签发。这里还要提一个实际开发中的调整:微信官方已经调整了手机号快速填写组件的获取方式,旧的 getPhoneNumber 返回 encryptedData 的流程已经失效,新方案是让用户点击授权按钮,后端通过 code 换取手机号。所以这套系统的手机号绑定我改成了“用户手动输入 + 短信验证码”的兜底方案,演示时也更稳定。
2.2 商品浏览和购物车:数据结构才是核心
商品模块看起来简单,实际做起来有几个地方容易踩坑。首先是商品表设计,我采用了经典的三表结构:商品主表(goods)、商品图片表(goods_image)、规格表(goods_sku)。商品主表只存商品基本信息:商品名称、描述、价格区间、销量、库存总量、上下架状态。规格表设计普通用户的基本需求,存颜色、尺寸等规格名和对应价格、库存。SKU 表存在的意义是,用户选择“红色/XL”这个具体规格时,价格和库存都是这个规格独有的,而不是整个商品统一的。代码里前端在选择规格时会调用一个“查询规格库存”的接口,返回当前选择的规格是否有货,无货的规格置灰不可选。
购物车的设计我建议用两张表:购物车主表(只存用户ID、总价、选中状态)和购物车明细表(存具体商品、SKU、数量、是否选中)。为什么不只用一张表?因为购物车要批量结算的时候,需要快速统计选中商品的总体信息,明细和主表分开后,改数量、删除、勾选这些操作都更清晰。选中状态很重要,结算时只算勾选中的商品,这个状态存在明细表里,避免了前端状态和后端不同步的问题。加购接口的幂等性也要注意,同一用户、同一商品、同一SKU,如果已存在就增加数量,而不是再插一条新记录。
一个值得注意的细节:库存的扣减不要在前端做。有的同学图省事,在小程序端判断数量是否大于库存,然后直接把数量传过去让后端更新。这种做法完全不安全,因为前端的所有判断都可以被绕过。正确做法是后端在“生成订单”这个事务里,用条件更新语句来扣库存,比如:
java复制@Transactional(rollbackFor = Exception.class)
public Order createOrder(Long userId, List<CartItemVO> items) {
// 1. 生成订单主记录
// 2. 遍历购物车明细,生成订单项
// 3. 扣减库存:UPDATE goods_sku SET stock = stock - #{num} WHERE id = #{skuId} AND stock >= #{num}
// 4. 清空对应购物车明细
}
第 3 步是关键中的关键。WHERE 条件里加上 stock >= num,如果影响行数为 0,说明库存不够,直接抛异常回滚整个事务。这个“乐观锁式扣减”能有效防止高并发下的超卖问题。不要把库存扣减放到下单之后改库存,也不要用先查再减的方式,那两步操作之间必然有时间窗口,并发一上来就会出现库存负数。
2.3 订单状态机:别小看这几个字段
订单模块是整套系统里业务逻辑最密集的地方。订单表的核心字段包括:订单号、用户ID、订单状态、商品总金额、优惠金额、实付金额、收货人信息、支付时间、发货时间、完成时间、取消时间。订单号我习惯用日期 + 随机数生成,比如 20250106153012001,这个格式可读性好,也方便按天分表时做路由。
订单状态我用整数表示:0 待付款、1 待发货、2 待收货、3 已完成、4 已取消、5 已退款。状态流转有明确的规则,不是随便改的。待付款可以取消,也可以模拟支付后变成待发货;待发货可以修改发货信息;待发货后用户不能直接取消,要申请退款;待收货可以确认收货变成已完成。这些规则在后端 Service 层统一校验,前端 UI 只负责展示当前状态可用的按钮。建议在数据库设计时就把这些状态用 tinyint 存起来,注释写清楚每个值代表什么,另外建一张“订单状态日志表”记录每次状态变化的轨迹。这个日志表在实际开发中非常有用,排障时能清楚地看到订单在哪个环节出了岔子。
关于支付,这套系统默认使用“模拟支付”模式,也就是点击“支付”按钮后,后端直接模拟支付成功回调,将订单状态改为待发货。这样做的原因是,接入微信支付需要商户号、证书、回调域名等一系列资质,对于学习阶段的项目来说,这些环境准备就会劝退一大半人。但代码里我保留了支付接口的扩展位,注释里标注了将来接入真实微信支付时需要在哪些位置补充统一下单和回调验签。如果你真的要做商业项目,记得把微信支付的 API 证书路径、商户号配到配置中心或者环境变量里,不要硬编码进代码。
2.4 搜索、分类、分页:列表页的性能与体验
首页和分类页的列表接口,看似简单,其实有几个地方要做好。第一个是分页参数,我统一用 pageNum 和 pageSize,通过 MyBatis-Plus 的分页插件处理,返回结构里包含总条数、总页数、当前页数据。小程序端用 onReachBottom 触发触底加载,把当前页码加一再请求一次,把新数据拼接进原有列表。这里有个体验细节:每次触底请求时,判断“当前页数是否已经大于等于总页数”,如果是就不再发起请求,否则用户拉到页面底部时会一直发空请求,白白吃流量。
第二个是搜索和排序。搜索条件包括商品名称的关键字模糊匹配,支持分类ID过滤。排序支持按销量、按价格升序、按价格降序、按新品。利用 MyBatis-Plus 的 QueryWrapper 可以轻松实现动态 SQL,根据前端传的 sort 字段,动态决定 orderBy 的列和方向。不要把这些逻辑写在 Controller 里,封装到 Service 层,这样多个入口(首页、分类页、搜索页)都能复用。
第三个是首页轮播图。轮播图数据我放到了单独的配置表里,后台管理系统可以动态维护,前端首页直接调接口拿图片列表。很多练手项目把轮播图写死在代码里,演示是没问题,但一体现“在线购物系统”的完成度就露馅了。动态配置才是真实的业务形态。
3. 文档体系:源码之外的硬功夫
3.1 需求文档和数据库设计文档怎么组织才有含金量
“文档”这两个字,很多人当成了应付差事,凑几页 Word 就算完。但真正的项目交付,文档是给接手人看的唯一可靠指引。我见过太多项目,代码写得挺好,但换个人接手,根本不知道哪里配置改了、哪里依赖了什么服务。所以我在这套系统里做了三份核心文档:需求文档、数据库设计文档、接口文档。
需求文档不能只粘贴功能列表,至少要有角色定义和核心业务流程。比如这套系统有普通用户和管理员两个角色:普通用户浏览商品、下单、查看订单;管理员管理商品、处理订单发货。然后把购物流程写清楚:搜索商品、加入购物车、确认订单、选择地址、支付、查看物流、确认收货、评价。每一条流程都用文字描述清楚前置条件、主流程、异常分支。核心用例的优先级标注也要有,P0 表示系统必须具备的功能,P1 是优化功能,这样排期时一目了然。
数据库设计文档我是配合表格来写的。每张表都要列出字段名、类型、默认值、是否可空、注释,同时还要有表与表之间的关系描述。用户表和订单表是一对多,订单表和订单项表是一对多,购物车表和购物车明细是一对多,商品表和 SKU 是一对多。光写字段还不够,建议把索引设计也写进去,比如订单状态索引、商品名称全文索引、用户ID索引,这些在实际运行中直接影响查询速度。我自己习惯把建表 SQL 的注释写得无比详细,生成文档时直接用工具导出,效率很高。
3.2 接口文档:写给前后端共同遵守的契约
接口文档的目的,是让前端同学不看后端代码也知道怎么调用。这套系统的接口文档大概有三十多个接口,覆盖登录、商品、购物车、订单、地址、用户信息。每个接口我都用统一格式记录:请求方法、请求路径、请求参数、返回结果、错误码。
以“加入购物车”接口为例,文档里会写清楚 POST /api/cart/add,请求参数是商品ID、SKU ID、数量,返回结果是购物车商品数量或者操作成功的标识。返回结果还要给一个示例 JSON,这样前端可以照着 mock 数据继续开发,不用等后端全部写完。错误码部分我统一用 200 表示成功,400 表示参数错误,401 表示未登录,403 表示无权限,500 表示服务器异常。业务层面的错误码则用自定义 code,比如 1001 库存不足、1002 商品已下架、1003 订单状态不允许当前操作。文档里附上一张“全局错误码表”,排障时可以快速定位问题方向。
3.3 部署文档:让从来没跑过这套系统的人也能顺利启动
部署文档是我觉得最容易被忽略却最能体现交付质量的部分。很多源码包下载下来只给一个 README,写两句“导入数据库,改配置,启动”。可实际上,环境变量没配、依赖版本对不上、JDK 版本不对,任何一个环节都会卡住半天。我的部署文档分四步写:环境准备、后端部署、小程序端配置、启动验证。
环境准备明确到 JDK 1.8 +、Maven 3.6+、MySQL 5.7+、Redis 5.0+,并附上每个软件的下载地址和安装注意点。后端部署步骤详细到用 Idea 打开后端工程后先 Maven 刷新依赖、再改 application.yml 里的数据库密码和 Redis 地址、然后通过 ShoppingMallApplication 主类启动。小程序端配置则明确写出:用微信开发者工具导入前端目录,AppID 可以先用测试号,然后在 utils/config.js 里把 baseUrl 改成你自己电脑的局域网 IP,比如 http://192.168.1.100:8080,最后在微信开发者工具的“详情-本地设置”里勾选“不校验合法域名”。启动验证就更重要了,文档会列一个检查清单:后端 Swagger 地址能打开、小程序首页能显示商品列表、登录接口能返回 token、模拟下单流程能走通。只有这个清单全部通过,部署才算完成。
4. 从0到1跑通:环境搭建与调试实操
4.1 后端的启动流程:Idea 里那几步别省
很多人在“启动后端”这一步就折了。我把常见的启动过程完整走一遍:第一步,在 Idea 里导入后端工程,如果用的是 pom.xml 方式,直接 Open as Maven Project,等依赖下载完。这里有个省心的技巧,Maven 仓库最好用阿里云的国内镜像,不然下载慢到怀疑人生。第二步,修改 application.yml 里的数据库连接,注意 MySQL 驱动版本要和 MySQL 安装版本匹配,5.7 的库用 com.mysql.jdbc.Driver,8.0 的库用 com.mysql.cj.jdbc.Driver。第三步,确认 Redis 启动了,因为登录鉴权和缓存依赖它。第四步,启动 ShoppingMallApplication,看到日志输出 Started Application in x.xxx seconds 后,用 Swagger 测试接口。
有个细节我每次都会提醒:application.yml 里时间时区一定要设置。推荐配置是 serverTimezone=Asia/Shanghai,不设置的话,MySQL 驱动 8.0 默认使用 UTC 时区,会导致后端查询数据库的时间比北京时间早 8 个小时,表现为订单时间、访问时间全部错乱,排查起来很隐蔽。
4.2 小程序端的运行配置:Ip 地址和域名校验是第一个坎
小程序连不上本地后端,这是最常出现的问题。根本原因是手机真机访问电脑上的服务,不能用 localhost,必须要用电脑的局域网 IP。步骤是先 ipconfig(Windows)或 ifconfig(Mac)查看本机局域网 IP,然后把它填进前端配置的 baseUrl。如果手机和电脑不在同一个 Wi-Fi 下,无论怎么配都是白搭。另外,微信开发者工具里要在“详情 -> 本地设置”勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,不勾选这个,所有 http:// 请求都会被拦截,提示 request:fail。
我建议在写代码之前就把这个配置理清楚,否则上线到真实环境时又要改动。实际开发时,我通常会在 config.js 里根据环境变量区分开发环境、测试环境、生产环境的 baseUrl,避免每次切换环境都要改代码再重新编译。比如:
javascript复制// config.js
const ENV = 'dev';
const BASE_URL = {
dev: 'http://192.168.1.100:8080',
test: 'https://test-api.example.com',
prod: 'https://api.example.com'
}
module.exports = { BASE_URL: BASE_URL[ENV] }
4.3 调试工具与前后端联调技巧
微信开发者工具自带的能力值得充分使用。它有 Network 面板查看每个请求的细节:请求头、请求体、响应体、耗时,这个面板在联调时是定位问题的第一利器。请求报错时,第一步不要去看代码,先在 Network 里看后端返回的到底是什么。后端返回 500,基本是后端逻辑异常,去后端日志看堆栈;返回 404,检查接口路径和路由是否正确;返回 504,多半是后端服务没启动或者 IP 端口不对。Console 面板除了看前端报错,还可以配合 console.log 输出关键变量的状态,方便观察数据流。
后端方面,Idea 的断点调试在联调阶段非常关键。我举一个真实场景:前端传了商品ID,后端查询商品详情时返回 null。这时候在 Service 层打个断点,看传入的商品ID是多少,数据库里是不是真有这个商品,如果数据存在却查不出来,检查表名、字段名是否对应。MyBatis-Plus 默认开启驼峰转换,但数据库的表名、字段名下划线命名和实体的驼峰命名必须一致,否则查询结果会全为 null。这类字段映射问题用断点几乎瞬间就能查出来。
5. 调试实录:我踩过的坑与排查套路
5.1 首页商品列表加载不出来:一份 Request 日志解决 80% 问题
最经典的场景是首页白屏、控制台报 request:fail。这种问题九成出在域名校验和网络地址上。第一次做联调的人多半会卡在域名校验,因为开发者工具默认连 http:// 都拦。处理思路:先看控制台报错提示是 url not in domain list 还是 request:fail,前者去勾选不校验合法域名,后者检查后端服务是否启动、IP 对不对、手机和电脑是否同一局域网。我建议在公司或学校网络环境里调试时注意,有些严格的路由器或防火墙设置会阻断局域网内端口访问,这时候需要暂时切换网络环境。
第二个经典场景是请求能通,但返回的数据格式和前端解析不一致。举个例子,后端返回 { code: 200, data: { list: [...] } },但前端的 res.data.data.list 却取不到。这种时候不要猜,在 Network 面板里看 Response 的原始结构,再对照前端取数据的路径,对不对一目了然。很多时候不是数据不存在,而是后端把结果包装了一层 data,前端多取或少取了一层。
5.2 下单失败:库存扣减与事务管理
经常有同学跑完流程,发现点击“提交订单”后提示下单失败,但控制台又没有明显报错。这种问题的常见原因有几种:第一种是购物车明细里商品状态是下架或者库存不足;第二种是购物车明细数据为空,提交订单时后端遍历不到数据;第三种是事务没有正确回滚,导致部分数据写入。
排查技巧是在后端 Service 的下单方法里加日志,每完成一步就打一行日志,比如:查购物车明细数量、计算总价、扣减库存影响行数。这样能清楚地看到在哪个环节抛出的异常。如果是“库存扣减影响行数为 0”,说明库存不足,返回提示“商品库存不足”即可;如果是事务回滚异常,检查 Service 方法的 @Transactional 注解是否被同级类方法调用绕过。这里有个 Spring 的经典坑:相同类内部调用方法时,@Transactional 不会被 AOP 代理捕获,事务会失效。如果下单逻辑是在同一个类内部调用了事务方法,一定要拆到不同的 Service 类,或者通过注入自己的代理来调用。
5.3 微信基础库版本、浏览器兼容性、iOS 时间格式等“小怪”
最后整理一些零碎但高频的问题。微信小程序的“基础库版本”在开发者工具里可以切换,有些 API(比如 wx.getMenuButtonBoundingClientRect)在老版本基础库上不支持,建议在 app.json 里声明 "libVersion": "3.x.x" 或者按文档要求设置最低基础库版本。iOS 上常见的坑是时间格式:new Date("2025-01-06 15:30:00") 在 iOS 上解析会失败,因为 iOS 只支持 new Date("2025-01-06T15:30:00"),处理时建议统一用 .replace(/-/g, '/') 或者直接后端返回时间戳。小程序单选框默认样式比较朴素,需要自定义样式时不要盯着原生组件的属性硬改,建议用 icon 加 label 组合实现。
我把排查套路整理成了速查表,方便你直接对照:
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
控制台报 request:fail |
域名未校验、IP错误、后端未启动 | 勾选不校验合法域名,检查局域网IP和后端启动状态 |
| 请求 200 但页面空白 | 数据层级取错、字段名不一致 | Network 面板查看响应原始结构,核对前端取值路径 |
| 数据库有数据但查不到 | 表名或字段名映射不一致、时区问题 | 检查实体与表字段映射,配置时区 |
| 订单提交失败 | 库存不足、事务失效、购物车数据为空 | 后端加日志定位,检查事务注解和方法调用链 |
| iOS 时间显示 NaN | 时间字符串格式不兼容 | 后端返回时间戳,前端统一格式化 |
| 开发者工具正常但真机异常 | 基础库版本、网络地址、微信缓存 | 检查基础库版本,切换真机调试清缓存重试 |
书写到这里的最后,我再交代一点实操体会。项目交付给学生或者客户时,源码只是最低要求,调试记录和文档才是服务价值的体现。我自己整理这份项目档案时,每踩一个坑都会顺手把日志截图和解决方案记录下来,后来再跑类似项目,直接翻自己的笔记就能避开大部分问题。这套购物系统我前后优化过好几轮,从最初只有商品展示和购物车的基础版本,逐步加入订单状态机、搜索排序、地址管理、模拟支付,每一步都是围绕“真实可用”去做的。如果你也想在这个基础上扩展,建议优先考虑接入真实微信支付、增加后台管理系统、加入优惠券模块,这三个方向最能提升商业完整度。小程序商城这条路,边界比你想的宽,但起步的核心还是把这套基础链路跑熟跑透。
