news 2026/9/1 1:36:05

Java + uni-app开源商城系统全栈拆解与部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java + uni-app开源商城系统全栈拆解与部署实战

简介:这是一套面向Java后端与uniapp前端开发者的新零售电商解决方案,专为快速搭建微信小程序商城而设计,适用于中小型电商项目开发、毕业设计及全栈技术学习者。资源包含前后端完整源码,后端基于Spring Boot+MyBatis实现用户管理、商品运营、订单支付等核心业务,前端采用uniapp框架统一构建多端界面,并深度适配微信小程序生态。压缩包共2000个文件,涵盖497个Java类(业务逻辑与数据访问)、659个JS脚本(交互与API调用)、55个Vue组件(可复用UI模块)、260个CSS样式文件(含iview、element、bootstrap等多套UI支持)以及32个wxml/wxss小程序原生文件,整体16.06MB,结构清晰、模块解耦。已有1170人学习下载,读者可直接部署运行,获取从数据库设计、RESTful接口开发、小程序页面渲染到微信支付对接的全流程实践能力,并参考配套JSON配置、SQL建表语句与MD文档快速上手。 如果你跟我说要做一个小程序商城,第一反应往往是:前端一套、后端一套、微信生态又一堆限制,光是把“登录、下单、支付”这三件事跑通,就够折腾好几个周末。更别提商品管理、购物车、订单状态这类看似简单、实际到处是细节的模块。所以当我看到这套 Java + uni-app 的前后端全部开源商城项目时,第一感受是:这东西能帮你省掉大量重复造轮子的时间,让你把精力放到真正重要的业务上。

这个项目最大的特点就是“全”——后端用 Java 写,前端用 uni-app 写,小程序端可以直接跑,源码全部公开。对正在学前后端分离开发的同学来说,它是一份很完整的学习资料;对想快速上线一个小程序商城的中小团队来说,它又是一个可以拿来改改就能用的基础工程。写这篇文章,我就围绕这套项目的整体设计、数据库、后端实现、前端页面、部署上线和常见坑位,做一次比较完整的拆解,希望对正在研究同类项目的朋友有帮助。

1. 项目整体设计与技术选型——为什么看中这套 Java + uni-app 组合

1.1 技术栈全景与选型逻辑

先列出这套项目里最核心的技术组件:

层级技术选型说明
后端语言Java 8+稳定,生态成熟,招聘市场上人多
后端框架Spring Boot 2.x快速构建接口,内置容器,适合前后端分离
ORMMyBatis-Plus单表 CRUD 很方便,复杂 SQL 也能手写
数据库MySQL 5.7+最常见的关系型数据库,成本低
缓存Redis登录态、验证码、热点数据缓存
前端框架uni-app(Vue 2/3)一套代码生成小程序、H5、App
状态管理Vuex/Pinia管理用户信息、购物车、全局状态
构建工具Maven、HBuilderX后端依赖构建、前端打包发行

选这套组合,我自己的判断有三点。

第一,Java 后端做电商类系统是有历史沉淀的。订单、支付、库存这些场景,Java 生态里有大量现成解决方案和开源组件,遇到问题很容易搜到答案。对比 Node.js 或 Go,Java 在中小团队里可能显得“重”,但稳定性和可维护性确实有优势。

第二,uni-app 对微信小程序的支持很成熟。它本质上是用 Vue 语法写一套代码,编译到不同平台。尤其微信小程序的登录、支付、分享这类能力,uni-app 都封装好了,虽然偶尔还是要写条件编译,但总体比原生小程序开发更高效。

第三,前后端分离已经是当前 Web 开发的主流形态。后端只出接口,前端负责页面渲染和交互。这套项目本身就是一个很好的前后端分离实战样例,接口怎么设计、请求怎么封装、鉴权怎么做,都能直接看到。

1.2 项目目录结构与模块规划

我拿到源码后,首先看的就是目录结构。一个结构清晰的项目,能省去很多理解成本。

后端部分大致长这样:

mall-server ├── src/main/java/com/mall │ ├── config // 全局配置:跨域、拦截器、微信参数 │ ├── controller // 接口层,接收前端请求 │ ├── service // 业务逻辑层,核心逻辑都在这里 │ ├── mapper // MyBatis 数据访问层 │ ├── entity // 数据库实体类 │ ├── dto // 请求参数对象 │ └── vo // 返回给前端的视图对象 ├── src/main/resources │ ├── mapper // MyBatis XML 文件 │ └── application.yml // 数据源、Redis、微信配置 └── sql └── init.sql // 数据库初始化脚本

前端部分大致长这样:

mall-app ├── pages │ ├── index // 首页 │ ├── category // 分类页 │ ├── cart // 购物车 │ ├── user // 个人中心 │ ├── goods // 商品详情 │ ├── order // 订单确认、订单列表 │ └── address // 收货地址 ├── api // 接口请求封装 ├── utils // 工具函数 ├── static // 静态资源 ├── App.vue ├── main.js ├── manifest.json // 小程序 appid、权限配置 └── pages.json // 页面路由和 tabBar 配置

这种分层比较常规,但它符合大多数团队的习惯:controller 只做参数接收和返回,业务逻辑全部放 service,mapper 只负责数据库交互。我看过不少开源项目把业务逻辑写在 controller 里,一个接口几百行,改起来非常痛苦。这套项目至少在分层上没有走偏,对新手来说也更容易读懂。

2. 商城核心业务模块拆解与数据库设计

2.1 用户端与管理端功能清单

商城的核心业务,无论大小,基本都绕不开这些模块:

  • 用户端:微信登录、首页轮播、商品分类、商品列表、商品详情、购物车、下单、微信支付、订单查询、售后申请、收货地址管理、优惠券领取
  • 管理端:商品上架/下架、分类管理、库存管理、订单发货、退款处理、基础数据统计

这套开源项目一般会包含用户端完整功能和简单管理后台。如果管理后台没做得很完整,也不用太担心,因为商城最核心的链路是“浏览商品→加购物车→下单→支付→发货→收货”,把这套链路跑通,剩下的都是锦上添花。

2.2 数据库表结构与关键字段设计

我把这套项目里比较核心的几张表列一下,字段不必完全照抄,重点是理解设计思路:

用户表(user)

字段类型说明
idbigint主键
openidvarchar(64)微信用户唯一标识
unionidvarchar(64)用户跨应用统一标识,可空
nicknamevarchar(64)昵称
avatarvarchar(255)头像地址
phonevarchar(20)手机号,可空
session_keyvarchar(64)微信会话密钥,敏感,注意加密
statustinyint状态:1 正常,0 禁用
create_timedatetime创建时间
update_timedatetime更新时间

商品表(goods)

商品表是电商系统里最核心的表之一。字段一般包括:商品名称、副标题、分类 ID、主图、详情富文本、价格、库存、销量、上下架状态、逻辑删除标记。价格必须用 decimal,不能用 float/double,否则会有精度问题。

SKU 表(sku)

如果商品有规格,比如颜色、尺码,那必须有 SKU 表。一个商品对应多个 SKU,每个 SKU 有自己的价格和库存。有些简单的商城项目没有 SKU 表,商品直接一个价格一个库存,但真正要商用,SKU 是绕不开的。

订单表(order)

字段类型说明
order_novarchar(32)订单号,唯一索引
user_idbigint下单用户
total_amountdecimal(10,2)订单总金额
pay_amountdecimal(10,2)实付金额
pay_statustinyint支付状态:0 未支付,1 已支付
order_statustinyint订单状态:待付款、待发货、待收货、已完成、已关闭
address_snapshotvarchar(500)收货地址快照 JSON
pay_timedatetime支付时间
create_timedatetime下单时间

订单明细表(order_item)

订单明细记录每个订单里的具体商品。关键点是:商品名称、价格、图片、数量都要冗余存储。为什么?因为商品信息后续可能会改,甚至可能被删除,但订单里的快照必须保留,否则用户查历史订单时看到的商品信息会变。

购物车表(cart)

购物车表相对简单:用户 ID、商品 ID、SKU ID、数量、选中状态。但要注意一点:购物车数据可以存数据库,也可以存 Redis。存数据库实现简单,但频繁读写压力大;存 Redis 性能好,但需要考虑持久化和数据一致性问题。这套项目如果用了 Redis,把购物车放 Redis 也是一个合理方案。

2.3 数据库设计里容易被忽略的几个关键点

第一,订单状态和支付状态要分开。很多新手会把这两个字段合成一个,比如“已支付”就算“已发货”,结果后续退款、售后的时候状态根本不够用。分开之后,订单状态管履约流程,支付状态管资金流,逻辑清晰很多。

第二,金额运算全部用分为单位或 decimal。与钱相关的计算,前端展示用元,后端计算用分,避免浮点误差。

第三,库存扣减要用条件更新。比如下单时执行:

update goods set stock = stock - #{num}, sales = sales + #{num} where id = #{id} and stock >= #{num}

这样利用数据库行锁保证不会超卖。如果先查库存、再判断、再更新,并发场景下肯定会出问题。

3. 后端 Spring Boot 实现要点与代码还原

3.1 微信小程序登录与 token 体系

商城第一个要解决的问题就是“用户是谁”。微信小程序提供了 wx.login 拿到临时 code,后端拿 code 去微信接口换 openid 和 session_key,然后确定用户身份。

整个流程是:

  1. 前端调用uni.login拿到 code
  2. 前端把 code 传给后端/api/auth/login
  3. 后端用 code 请求微信jscode2session接口
  4. 微信返回 openid 和 session_key
  5. 后端查数据库,用户不存在则自动注册
  6. 后端生成 token,存 Redis,返回给前端
  7. 前端把 token 缓存起来,后续请求都带上

我见过不少项目把 openid 直接返回给前端,这是不合适的。openid 是用户在小程序里的唯一标识,相当于用户的身份证号,前端拿到它没有意义,反而增加了泄露风险。正确做法是后端生成一个随机的 token 作为登录凭证,openid 只保存在服务端。

核心代码大致是这个意思:

@PostMapping("/auth/login") public Result login(@RequestBody LoginDTO dto) { // 1. 调用微信接口换取 openid String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + appid + "&secret=" + secret + "&js_code=" + dto.getCode() + "&grant_type=authorization_code"; WechatSession session = restTemplate.getForObject(url, WechatSession.class); // 2. 根据 openid 查找用户,不存在则注册 User user = userMapper.selectByOpenid(session.getOpenid()); if (user == null) { user = new User(); user.setOpenid(session.getOpenid()); userMapper.insert(user); } // 3. 生成 token 存入 Redis,设置过期时间 String token = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set("token:" + token, user.getId(), 7, TimeUnit.DAYS); return Result.ok(token); }

拦截器里要校验 token:

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("token"); if (StringUtils.isBlank(token)) { throw new BusinessException("未登录"); } Object userId = redisTemplate.opsForValue().get("token:" + token); if (userId == null) { throw new BusinessException("登录已过期"); } request.setAttribute("userId", userId); return true; }

这里有个很容易踩的坑:Redis 里存的 key 如果没用前缀区分业务,不同模块之间可能互相覆盖。所以 token 的 key 一定要加上业务前缀,比如token:xxxx

3.2 商品列表与购物车接口设计

商品模块的接口一般包括:

接口方法说明
/api/goodsGET分页查询商品,支持分类筛选、关键字搜索
/api/goods/{id}GET商品详情
/api/cart/listGET购物车列表
/api/cart/addPOST加入购物车
/api/cart/updatePUT修改购物车商品数量
/api/cart/checkedPUT修改选中状态
/api/cart/deleteDELETE删除购物车商品

接口设计上要注意:列表接口不要一把梭把所有字段查出来,要分页。详情接口要包含富文本、轮播图、SKU 列表等完整信息,但用不着把浏览量、点击量这些非核心字段全塞进去。

购物车里“选中状态”这个字段容易被人忽略。很多商城购物车是支持勾选一部分商品去结算的,所以购物车表里要有 checked 字段。后端在下单时,只处理选中状态的商品。如果把这个逻辑放到前端处理,用户切换设备后数据就不一致了。

3.3 订单生成与微信支付对接

订单是整个商城里最复杂的链路,也最容易出问题。我把流程拆开写:

创建订单

  1. 接收请求,从购物车中读取选中的商品,或者直接传商品 ID 和数量
  2. 校验商品是否存在、是否上架、库存是否充足
  3. 生成唯一订单号
  4. 计算订单总金额
  5. 插入订单表和订单明细表
  6. 扣减库存
  7. 清空对应购物车数据

订单号生成有一些讲究。最简单的方案是用时间和随机数拼接,但要注意并发重复。更稳妥的方式是:日期 + 用户 ID + 随机数,或者直接用 Redis 自增序列。订单号要有唯一索引,防止重复。

微信支付

微信支付流程对第一次接触的同学来说有点绕。我来理清一下。

第一步,后端调用微信支付“统一下单”接口,传入业务参数:订单号、金额(单位是分)、用户 openid、支付回调地址。微信返回一个 prepay_id。

第二步,后端拿着 prepay_id 和时间戳、随机字符串、签名一起返回给前端。

第三步,前端用uni.requestPayment调起微信支付面板。

第四步,用户输入密码完成支付,微信服务器异步通知后端的回调地址。

第五步,后端收到回调,先验签,再校验订单号和金额,最后更新订单状态为已支付。

回调处理是重中之重。微信支付回调可能因为网络原因重复发送,所以服务端处理必须幂等。也就是说,同一个订单收到多次回调,最终结果必须一样,不能因为回调两次就把订单状态覆盖成“已发货”或者重复加积分。

我在项目里见过一个很低级的错误:回调里直接把订单状态改成“已支付”,但没判断订单当前状态。结果用户在未支付状态下点了取消,然后微信回调到了,订单状态又变成了已支付,最后用户的订单要人工介入才能处理。正确做法是:只有在订单原状态是“待付款”时才更新为“已支付”,其他情况直接忽略。

回调接口代码大致是这样:

@PostMapping("/api/pay/notify") public String payNotify(HttpServletRequest request) { // 1. 读取回调数据 String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); // 2. 解密验签,确认是微信发的 Map<String, String> params = WxPayUtil.decrypt(body); if (!WxPayUtil.verifySign(params)) { return "fail"; } // 3. 取出订单号和金额 String orderNo = params.get("out_trade_no"); String totalFee = params.get("total_fee"); // 4. 查订单,校验金额 Order order = orderMapper.selectByOrderNo(orderNo); if (order == null || !order.getPayAmount().equals(new BigDecimal(totalFee).divide(new BigDecimal(100)))) { return "fail"; } // 5. 幂等更新 int rows = orderMapper.updatePayStatus(orderNo, 1, 0); if (rows == 0) { return "success"; } // 6. 其他业务:通知发货、加积分等 return "success"; }

这里要注意,回调接口返回给微信的必须是successfail字符串,不能返回 JSON。微信只有收到success才认为通知成功,否则会持续重试。

3.4 并发场景下的库存扣减

如果你只做演示,库存扣减随便写写没关系。但真实商城,超卖是必须解决的问题。

最常见的做法有两个:

第一种是数据库乐观锁。商品表加一个 version 字段,更新时带上 version,version 不匹配就更新失败。但这种方案在库存扣减这种场景下显得绕,而且重试逻辑要自己写。

第二种是条件更新,也就是我前面写的where stock >= #{num}。利用数据库行锁,同一时刻只有一个事务能更新成功,天然防超卖。大多数中小商城用这种方式足够了。

如果你的并发量真的很高,再考虑 Redis 预扣库存,但这套方案的复杂度会大幅提升,普通项目没必要。

4. 前端 uniapp 页面实现与微信小程序适配

4.1 页面结构、tabBar 与分包加载

前端页面结构里,最常见的四个 tab 是:首页、分类、购物车、我的。在pages.json里配置 tabBar,对应这四套页面。

商城应用不是只有这几个页面。商品详情、订单确认、订单列表、支付结果页、地址管理、售后页,这些页面都属于低频访问页面。在微信小程序里,主包体积限制是 2M,超了就得用分包。所以页面规划时,可以把高频页面放在主包,低频页面放分包。

分包有两点要注意:

第一,分包不能嵌套,只能是主包和一级分包这种结构。

第二,页面跳转时,如果目标页面在分包里,用uni.navigateTo传入分包路径即可,不需要额外配置。但如果要访问分包里的组件,那就涉及“分包异步化”的问题,需要配置。

对于商城来说,如果商品图片不多、代码也不复杂,第一阶段不分包也能过,但建议从一开始就做好目录规划,避免后期迁移成本。

4.2 请求封装与登录态处理

前后端分离的核心就是接口调用。uni-app 里用uni.request发起请求,但它是个回调函数,如果不做封装,页面里会写一堆重复代码。

我自己习惯把请求封装成一个 Promise 方法,统一处理 baseURL、token、错误码、加载动画:

// utils/request.js const BASE_URL = 'https://api.example.com' export function request(options) { return new Promise((resolve, reject) => { uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { token: uni.getStorageSync('token') || '' }, success: (res) => { if (res.data.code === 0) { resolve(res.data.data) } else if (res.data.code === 401) { // token 过期,重新登录 uni.navigateTo({ url: '/pages/login/login' }) reject(res.data) } else { uni.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data) } }, fail: (err) => { uni.showToast({ title: '网络异常', icon: 'none' }) reject(err) } }) }) }

关于登录态,有一个很常见的坑:小程序里wx.login生成的 code 是一次性的,并且有效期很短,前端不能把 code 存起来备用。每次登录都必须在需要时重新调用。

另外要区分两个“登录”概念:

一个叫静默登录。用户进入小程序,后端自动用 code 换 openid 创建用户,这个过程用户无感知。用户进来就已经是登录状态了。

另一个叫主动授权登录。用户点击“微信登录”按钮,前端调用授权弹窗,拿到用户头像昵称,再调后端接口更新用户资料。

几乎所有商城都建议先做静默登录,不然用户一进来就看到一堆需要登录的提示,体验非常差。

4.3 首页、商品详情、购物车与订单页实现

首页:通常由搜索框、轮播图、金刚区图标、商品推荐列表组成。数据来源是后端接口:轮播图接口返回 banner 列表,商品接口返回推荐商品。首页不需要太复杂,重点是首屏加载速度。图片要压缩,列表要分页,不能一次查几千条数据。

商品详情页:核心数据包括商品信息、轮播图、价格、销量、库存、SKU 规格选择、图文详情。SKU 选择的逻辑稍微麻烦一点:用户选了某个规格,要计算哪些 SKU 是可选的,哪些是缺货下架的。简单做法是遍历所有 SKU,匹配用户当前已选规格,能匹配的显示可点击,否则置灰。

购物车页:除了基本列表,还要处理全选、单选、数量加减、左滑删除。购物车底部的“合计金额”要在前端实时计算,但真正下单时以后端计算为准。前端可以先给用户一个预估,后端做最终校验。

订单确认页:用户从购物车或商品详情页跳过来,展示收货地址、商品清单、运费、总金额,点击“提交订单”后调后端创建订单接口,成功后调起支付。

支付:代码比较简单:

uni.requestPayment({ provider: 'wxpay', timeStamp: res.timeStamp, nonceStr: res.nonceStr, package: res.package, signType: 'MD5', paySign: res.paySign, success: () => { uni.redirectTo({ url: '/pages/order/paySuccess' }) }, fail: (err) => { // 用户取消支付,跳到订单列表让用户重新支付 } })

有个细节要注意:package参数的值一定是prepay_id=xxx这个完整格式,很多新手只传了prepay_id,导致调起支付失败。

4.4 微信小程序特有的适配坑

微信小程序虽然上手简单,但适配问题不少。

rpx 和 px 的换算:小程序里 750rpx 等于屏幕宽度,开发时不用担心不同机型。但如果页面里嵌入了第三方 H5 或者原生组件,尺寸单位就还得自己处理。

安全区适配:iPhone X 以后的机型底部有 home 条,页面底部按钮会被遮挡。处理方式是在底部容器加 padding:

padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);

图片域名必须配白名单:小程序里<image>标签加载的图片,域名必须在小程序后台配置为“downloadFile 合法域名”,而且必须 HTTPS。开发模式下可以在“详情→本地设置”里勾选“不校验合法域名”,但上线前一定要配置好。

头像昵称填写规则:现在微信不再支持wx.getUserInfo直接弹窗拿头像昵称,而是推荐用buttonopen-type="chooseAvatar"inputtype="nickname"让用户主动填写。如果你还在用老方法,审核大概率会挂。

隐私协议弹窗:微信对用户隐私的管控越来越严,小程序中用到的隐私接口要先声明。到时间节点后,强制要求隐私弹窗授权,商城这种收集用户昵称头像的手机号的项目,一定要提前把隐私政策页面做好。

5. 本地搭建、部署上线与二次开发实操指南

5.1 从拉取源码到本地跑通前后端

我按照实际流程走一遍,告诉你怎么在本地把项目跑起来。

第一步,准备环境:安装 JDK 1.8+、Maven、MySQL 5.7+、Redis、HBuilderX、微信开发者工具。

第二步,导入数据库。项目里一般会有sql/init.sql或者doc/init.sql,在 MySQL 里执行,创建数据库和表。如果提供了初始化数据,那就更好,首页不至于一片空白。

第三步,修改后端配置。打开application.yml,把数据源地址、账号、密码改成你自己的,Redis 地址也改一下。如果有微信相关配置,先填上测试用的 appid 和 secret(没有的话,登录功能暂时不可用,但其他接口能测)。

第四步,启动后端。在项目根目录执行:

mvn spring-boot:run

后端起来之后,先访问一下接口文档,比如http://localhost:8080/api/goods,能返回 JSON 就说明环境没问题。

第五步,用 HBuilderX 导入前端项目。在manifest.json里配置小程序 appid,然后运行到微信开发者工具。

第六步,微信开发者工具打开后,点击“详情→本地设置”,勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。这是因为本地调试用的通常是 http 地址,微信默认禁止。

到此,前后端就打通了。

5.2 前后端分离联调与接口自测

前端开发中用的接口地址和后端本地地址不一样,怎么解决?两种方式。

第一种,在request.js里把 baseURL 拆成多个环境变量:

const ENV = 'dev' const BASE_URL = { dev: 'http://localhost:8080', prod: 'https://api.example.com' }[ENV]

第二种,用 HBuilderX 里常见的方式:开发环境请求走本地局域网 IP,比如http://192.168.1.100:8080,真机调试时手机和电脑在同一 Wi-Fi 下就能访问。

接口自测推荐用 Apifox 或 Postman。先把后端接口导出来,所有接口测通过之后再联调前端,能省非常多时间。

5.3 生产环境部署

本地跑通只是第一步,上线才是真正的考验。上线流程大概是:

后端打包:

mvn clean package -DskipTests

生成 jar 包后,上传到服务器,用 Java 命令启动。更推荐的方式是写一个 Dockerfile 或直接用 docker-compose,把 MySQL、Redis、后端服务一起编排起来。一个小型商城用 docker-compose 管理三个容器就够了:

version: '3' services: mysql: image: mysql:5.7 restart: always environment: MYSQL_ROOT_PASSWORD: yourpassword MYSQL_DATABASE: mall volumes: - ./sql:/docker-entrypoint-initdb.d redis: image: redis:6 restart: always server: build: . restart: always ports: - "8080:8080" depends_on: - mysql - redis

Nginx 负责反向代理和 HTTPS 配置。小程序要求所有请求域名必须是 HTTPS,所以你需要一个已备案的域名,并配置 SSL 证书。Nginx 配置里把/api的请求转发到内网的 8080 端口。

前端打包:在 HBuilderX 里选择“发行→小程序-微信”,生成微信小程序代码包,在微信开发者工具里打开,点击“上传”,然后到微信公众平台提交审核。

还有图片存储的问题。本地存储图片在联调时没问题,但生产环境建议用对象存储服务,比如阿里云 OSS、腾讯云 COS,或者七牛云。商品图片、用户头像都放到对象存储,后端只存 URL。否则服务器磁盘会很快被打满,而且图片加载速度也会拖慢用户体验。

5.4 基于开源项目的二次开发建议

拿到开源项目后,怎么改才能不踩坑?我的经验是:先读懂,再动手。

第一步,把“用户登录→浏览商品→加购物车→下单→支付”这条主链路完整跑通,边跑边打断点,搞清楚每次请求经过哪些类、哪些方法。

第二步,不要上来就重构。开源项目的代码风格可能不符合你的习惯,但它能跑。等你完全理解每一行代码为什么这么写之后,再决定要不要改。

第三步,从一个小需求开始实践。比如给商品列表加一个“新品”标签,或者给订单列表加一个“取消订单”按钮。通过改一个小功能,你能快速熟悉项目的代码习惯和扩展方式。

常见的扩展方向:

  • 优惠券模块:后台发券、用户领券、下单抵扣
  • 秒杀模块:Redis 预扣库存 + 限流 + 异步下单
  • 分销模块:用户推广关系绑定、分销佣金结算
  • 多商户支持:把商品表加一个商户 ID,订单结算时拆单

这些都是很成熟的场景,网上也有大量参考实现。

6. 常见问题与排坑记录

6.1 真机预览时请求失败

表现:开发者工具里页面正常,手机预览时首页空白、数据加载不出来。

原因基本是这么几个:

  1. 前端请求的 baseURL 是http://localhost,手机访问的是电脑的 localhost,当然是通的。要改成局域网 IP。
  2. 后端没监听0.0.0.0,只监听了本地回环地址,局域网访问不到。Spring Boot 默认是0.0.0.0,一般不会出这个问题,但如果你用了自定义配置,确认一下。
  3. 手机和电脑不在同一个网段,防火墙拦截了端口。

排查方法:先用手机浏览器访问一下http://电脑IP:8080/api/goods,能通说明后端没问题,问题在前端配置。

6.2 支付回调不触发或重复触发

回调不触发,去查这么几项:

  • 回调地址是不是公网可访问?本地开发没有公网地址,微信服务器根本调不到。可以先用内网穿透工具暴露本地端口。
  • 回调地址的路径和后端代码是否完全一致?
  • 回调地址是不是 HTTPS?微信支付回调要求 HTTPS(本地调试时可以临时用 HTTP 但生产必须 HTTPS)。

回调重复触发是正常现象,微信会重试,直到收到success。所以回调逻辑必须幂等。

签名验证失败,先检查密钥是否配置正确,再检查参数拼接顺序是否和微信文档一致。不同语言的签名实现细节不同,Java 里尤其要注意空值和大小写。

6.3 登录态突然失效

用户操作到一半被弹回登录页,体验非常糟糕。常见原因:

  1. token 过期时间太短。如果只是个小商城,token 有效期设 7 天比较合适。
  2. Redis 数据丢失。Redis 如果没有持久化,重启后所有 token 都没了,用户全部掉线。生产环境要开启 AOF 或 RDB 持久化。
  3. 前后端时间不一致。如果用了 JWT,签名验证时需要对比时间,服务器和客户端时间偏差大会导致 token 提前失效。

6.4 小程序提审被拒

小程序审核被拒,最常见的几条原因:

  • 类目选择不对。商城类小程序需要选择“电商平台”或“商家自营”类目,并上传相应资质。
  • 涉及虚拟商品支付。微信对虚拟支付管控很严,如果商城卖的是会员、课程、虚拟币这类虚拟商品,个人主体基本过不了。解决方案是走微信的小程序虚拟支付能力,或者做成 H5 支付。
  • 没有隐私政策。小程序里收集用户信息,必须有明确的隐私政策弹窗,说明收集了哪些信息、用来做什么。
  • 需要测试账号。如果管理后台登录需要账号密码,提审时要在后台配置体验账号,否则审核员进不去。

6.5 常见问题速查表

问题原因解决思路
真机加载不出图片图片域名没有加入 downloadFile 白名单,或不是 HTTPS后台配置白名单,图片改成 HTTPS
requestPayment 提示参数错误package 参数少了prepay_id=前缀检查参数拼装格式
下单提示库存不足库存扣减逻辑写成了先查后改改成条件更新 SQL
回调后订单还是待付款回调里没做幂等处理,订单状态被后续请求覆盖加状态判断,只有待付款才更新
购物车数据丢失购物车只存在 localStorage,用户换设备就没了购物车数据同步到后端
支付金额少了 0.01前后端金额单位不统一统一用分传给微信支付,展示时转元
页面跳转后数据没刷新页面 onLoad 只执行一次,再次进入时走了缓存在 onShow 里重新拉取数据

6.6 我的几点避坑经验

最后说几个我在实际开发中悟出来的经验,不算高深,但很实用。

第一,商城项目里,订单状态机的设计一定要想清楚再写代码。把“待付款→待发货→待收货→已完成”这条主链路和“已取消、售后中、已退款”这些分支画清楚,后面加功能、改 bug 都会轻松很多。我见过太多项目因为状态机混乱,改一个状态影响一片逻辑。

第二,日志必须打足。尤其是支付回调、订单状态变更这种关键节点,每一步都要有日志。出问题的时候,没有日志你会非常痛苦。

第三,不要把开源项目的代码当成完美的。开源项目解决的是通用问题,很多细节是参差不齐的。你在二次开发时,一定要自己把“登录→下单→支付”这条链路的关键逻辑完整读一遍,确认没有明显漏洞再上线。尤其是涉及钱的逻辑,怎么小心都不为过。

这套 Java + uni-app 的开源商城项目,像一套可以正常运转的样板间。你可以直接入住,也可以照着它的格局重新装修。最怕的是拿到源码之后一上来就这里改改那里删删,结果改了三天发现项目跑不起来了。从读懂到改好,中间没有捷径,但沿着“主链路走通→读关键代码→小改动试水→完整功能开发”的路线,收益会是最稳的。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 1:35:24

一文读懂Agent三大核心设计范式:ReAct / Plan-and-Execute / Reflection

导语 学习大模型智能体开发、应对AI面试、做Agent架构设计&#xff0c;最核心的基础就是三大经典Agent设计范式&#xff1a; ReAct、Plan-and-Execute、Reflection。 很多开发者普遍存在认知误区&#xff1a;混淆范式层级、把反思机制当成调试工具、不清楚不同范式的适配场景…

作者头像 李华
网站建设 2026/9/1 1:33:13

简历解析系统实战:从PDF到结构化数据的规则引擎设计

简介&#xff1a;本资源是一套面向计算机类本科生的毕业设计与课程作业级智能简历解析系统实现方案&#xff0c;聚焦人工智能在HR场景中的落地应用&#xff0c;解决传统简历筛选效率低、信息提取依赖人工等痛点。压缩包共458个文件&#xff0c;含305份真实简历样本&#xff08;…

作者头像 李华
网站建设 2026/9/1 1:28:24

基于STM32的电机状态检测实战:编码器测速、ADC采样与故障保护

在写嵌入式项目时&#xff0c;电机状态检测几乎是绕不开的一环。无论是智能小车、机器人底盘&#xff0c;还是小型工业设备&#xff0c;都需要知道电机当前是否在转、转速是多少、电流是否正常、有没有堵转或超温。很多初学者在做 STM32 电机控制时&#xff0c;往往只关注“怎么…

作者头像 李华
网站建设 2026/9/1 1:23:02

互斥锁管理之unique_lock学习

前面学习了std::lock_guard和std::scoped_lock,lock_guard的功能是自动解锁&#xff0c;能有效避免忘记unlock()的情况。但只能锁定一个互斥锁&#xff0c;scoped_lock比lock_guard高级&#xff0c;可以同时锁定多个互斥体。而unique_lock比它们更灵活。它‌独占管理互斥量所有…

作者头像 李华
网站建设 2026/9/1 1:22:22

基于微信小程序的景区实名核验与门票预约服务系统(程序+文档+讲解)

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华