
简介面向需要快速搭建微信小程序商城或学习Java全栈开发的开发者这套源码提供基于SpringMVCMyBatisSpringMavenMySQL架构的后端服务与H5/CSS3前端并配有基于Bootstrap-Ace框架的完整后台管理界面。功能覆盖商品发布、物流管理、评价系统、优惠券、运费系统、在线客服、在线支付与退款、微信管理等多个电商核心模块基本可满足中小型商城项目的二次开发与功能扩展需求。同时完整前后端代码有助于梳理微信支付/退款、客服会话、运费计算等业务流程理解小程序与后台服务之间的接口设计。该项目目录结构清晰前后端分层明确适合边读源码边对照梳理业务流程。资源采用7z格式压缩约20.5MB下载部署较为方便。目前已有5047人学习/下载适合具备一定Java基础、希望结合微信生态实践商城开发的中高级开发者直接参考。1. JAVA微信小程序商城源码一套能真正上线的代码长什么样做商城类小程序最怕拿到一个看着齐全、改起来处处受限的半成品前端能逛能加购物车后台却只有一个空壳订单、库存、支付链路全是缺口。JAVA微信小程序商城源码完整后台指的正是同一个项目里把微信小程序原生前端、JAVA后端服务和管理后台三件套一起交付前端有商城页面后端有商品、订单、会员、营销、权限等模块的接口与维护界面。它适合需要快速起盘的小团队、接私活的外包工程师以及已经在用Spring BootMyBatis做业务、不想重复造轮子的开发者。这套方案的价值关键不在代码量而在链路有没有闭环——从登录、下单、支付回调到库存扣减每一环都要在同一套代码里走通。2. 跑通前先拆结构小程序端和JAVA后台的代码边界2.1 小程序端优先看这四个目录拿到源码先别急着导入工具先看目录结构。原生微信小程序项目里pages目录放页面每个页面是.js.json.wxml.wxss四件套components目录放公共组件比如商品卡片、价格标签、数量选择器utils目录放工具函数重点看request.js和auth.jsconfig目录里则是baseUrl、AppID、支付参数这些运行配置。我一般先打开config里的配置文件确认服务端地址是写死还是按环境区分。很多源码会在utils/request.js里封装一层统一请求自动带token、统一处理401、把后端返回的{code, msg, data}解包。这一层决定了后面所有页面的调用方式也是二次开发最常动的地方。有的项目还会在app.js里做全局登录态初始化这个文件也优先看因为它决定用户进入小程序后是先跳登录页还是静默登录。// utils/request.js —— 小程序端统一请求封装 const { baseUrl } require(../config/index.js) function request(url, method GET, data {}) { const token wx.getStorageSync(token) return new Promise((resolve, reject) { wx.request({ url: baseUrl url, method, data, header: { Content-Type: application/json, // 后台靠这个请求头识别当前用户 Authorization: token ? Bearer token : }, success(res) { if (res.data.code 401) { // token过期清掉本地登录态触发重新登录 wx.removeStorageSync(token) wx.removeStorageSync(userInfo) reject({ code: 401, msg: 登录已过期 }) } else { resolve(res.data) } }, fail(err) { reject({ code: -1, msg: 网络异常 }) } }) }) } module.exports { request }这段封装的逻辑要点所有请求统一带Authorization头后台拦截器靠它判断用户是否登录401统一在请求层处理业务代码里不用到处写“判断登录过期”。baseUrl如果写localhost手机预览时会直接失败——我通常会在config里区分dev和prod两套值编译时切换而不是每次手动改代码。2.2 后台为什么用Spring BootMyBatis市面上流通的JAVA商城源码绝大多数落在Spring BootMyBatis这个组合上。选它不单是资料多、招聘匹配度高而是这套组合在“复杂查询”和“快速CRUD”之间平衡得最好。MyBatis的XML里写联表查询很直观分页插件PageHelper一行就能接上和微信小程序端“列表加载更多”这种场景天然匹配。服务分层通常是Controller→Service→Mapper三层Controller接HTTP参数Service写业务规则Mapper只做SQL。这也意味着后续改动最频繁的是Service层。下单时要扣库存、生成订单、记录流水Controller里只是一段调用业务全在Service里。如果二次开发时把SQL直接堆在Controller里后续维护成本会指数级上升。看一个典型的商品列表ControllerRestController RequestMapping(/api/goods) public class GoodsController { Autowired private GoodsService goodsService; /** * 商品分页列表小程序端滚动加载 */ GetMapping(/list) public JsonResult list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Integer categoryId, RequestParam(required false) String sortField) { // Service层内部用PageHelper做物理分页 PageResultGoodsVO result goodsService.pageQuery(page, size, categoryId, sortField); return JsonResult.ok(result); } }sortField参数用于按价格、销量、新品排序。后台不能直接把前端传的字符串拼进ORDER BY常见做法是维护一张白名单映射表把price、sales、new翻译成SQL列名匹配不到就返回默认排序。这个位置是SQL注入的高发区也是我审查源码时必看的一行。2.3 数据模型三张主表说清商城业务看懂表结构比看懂代码更快。商城项目围绕三张主表转用户表、商品表、订单表。用户表除微信相关字段外通常会放openid、unionid、手机号、余额、积分商品表一般是spusku两层spu代表“一款商品”sku代表“一个规格”比如红色加大码是一个sku库存挂在sku上而不是spu上。订单表则是主表子表订单主表存总金额、状态、收货地址快照订单子表存每个商品的sku、数量、单价、优惠明细。订单状态字段的设计直接决定后续代码好不好改。常见做法是用整数status表示状态机0待支付、1已支付待发货、2已发货、3已完成、4已关闭、5退款中、6已退款。用字符串枚举也可以但后期新增状态时老数据迁移永远比新代码难。主表核心字段关联关系useropenid, unionid, nickname, balance1对多订单表goods_sputitle, cover, status1对多goods_skugoods_skuspu_id, spec, stock, price1对多订单子表orderorder_no, user_id, total_amount, status1对多order_item如果源码自带多商户或分销模块表会更多但核心链路不变。看清这三张主表菜单栏里那些看着复杂的营销功能本质都是在给订单表加字段或加关联表。3. 本地部署到能下单两端的启动方法与联调姿势3.1 后台启动初始化SQL、改配置、跑起来在IDEA里以Maven项目方式导入源码根目录等依赖下载完成。源码根目录下一般有doc或db文件夹放着xxx.sql初始脚本。这个脚本最先执行它建库建表同时写入一批演示商品和后台管理账号。SQL文件里如果有CREATE DATABASE语句直接用Navicat或命令行整体执行没有的话手动建库并指定utf8mb4字符集再导入表结构。注意导入SQL前先确认字符集。商城商品名称、收货地址里会出现生僻字和emoji数据库字符集最好用utf8mb4而不是utf8否则后续写库直接报错。然后改application配置# application.yml —— 本地联调常用配置 server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 mall: wx: appid: 你的小程序AppID secret: 你的小程序Secret数据库、Redis、微信AppID三项是启动前必改的。AppID在本地联调时可以先填测试号但支付功能必须用已认证的小程序账号。Redis也没得省——商城源码里登录态、验证码、购物车基本都会用Redis本机没装Redis服务就直接启动失败。第一次启动建议先确认JDK版本和项目pom里Spring Boot版本匹配JDK 8还是11、Tomcat内嵌端口是否被占用都能从启动日志里一眼看出来。跑mvn spring-boot:run看到Tomcat started后台才算起来。启动报错分两类一是端口占用、Redis连不上这类环境问题二是数据库脚本没导全导致的表不存在。环境问题看报错前几行就能判断表不存在则要回头重导SQL不要手动建表去补否则字段对不上后面全是坑。3.2 小程序端导入与调试模式小程序端在微信开发者工具里以“小程序项目”方式导入填上自己的AppID。导入后先别急着改代码确认两件事一是工具右上角“详情-本地设置”里勾上“不校验合法域名”否则wx.request请求本地后台会被拦截二是把config里的baseUrl改成电脑的局域网IP而不是localhost——手机预览时localhost指向的是手机自己。// config/index.js —— 联调时切换环境 module.exports { // 开发环境电脑局域网IP 后台端口 baseUrl: http://192.168.31.42:8080/api, // 生产环境备案过的HTTPS域名微信要求不能带端口 prodUrl: https://api.yourdomain.com/api }改完baseUrl小程序端所有列表页就能拉到数据了。注意真机预览时手机和电脑要连同一个局域网防火墙要放行后台端口。请求还是红的话先到后台日志看有没有收到请求——没收到是网络不通收到了再往下查接口报错。3.3 登录态是怎么打通的wx.login到JWT小程序端首次进入页面调用wx.login拿到一个临时code把code发给后台后台拿code去微信的code2Session接口换openid和session_key。openid是用户在这个小程序里的唯一标识session_key用于解密手机号等敏感数据两者都不能暴露给前端。常见做法是后台不自己存session而是签发一个自定义token返回给小程序端前端把token存Storage之后每个请求在请求头带上。token方案要么是UUID存Redis要么是JWT。UUID方案token失效要查RedisJWT方案签发后过期前没法主动踢人各有利弊取决于业务需要。看前端侧的核心登录代码// utils/auth.js —— 静默登录流程 const { request } require(./request.js) function silentLogin() { return new Promise((resolve, reject) { // 已有token且没过期直接放行 const token wx.getStorageSync(token) if (token) { resolve(token) return } // 没有token走wx.login换临时code wx.login({ success: res { if (res.code) { // code发给后台后台换成我们自己的token request(/auth/login, POST, { code: res.code }) .then(data { wx.setStorageSync(token, data.token) wx.setStorageSync(userInfo, data.userInfo) resolve(data.token) }) .catch(reject) } else { reject(new Error(wx.login获取code失败)) } }, fail: reject }) }) }这段逻辑有两个要点wx.login的code只能用一次服务端换完openid后立即作废后端返回的token要设过期时间比如7天前端配合401拦截自动静默登录用户感知不到登录过程。很多源码出现死循环登录就是401处理里又触发wx.login旧token没清干净造成来回跳。4. 商城的钱都在代码细节里订单支付要处理的4个边界4.1 下单与库存扣减一句带条件的UPDATE下单业务最怕超卖——库存只剩1件两个用户同时下单却都成功了。常见做法不是先select库存再判断而是把扣减条件直接写进UPDATE语句UPDATE goods_sku SET stock stock - 1 WHERE id #{skuId} AND stock 0这段SQL执行后影响行数为1说明扣减成功影响行数为0说明库存不足或商品已下架直接返回“库存不足”。这种方式天然带乐观锁语义不需要select for update也不需要分布式锁。在MyBatis里更新影响行数用int接收判断rows 1即可。扣减成功后再插入订单如果订单插入失败要回滚事务并把库存加回去。事务边界要包住“查地址、扣库存、插订单、发消息通知”的全流程任意一步失败全部回滚。线上看到库存变负数基本都是直接把这段SQL改成了“先查后改”或删掉了stock 0条件。4.2 支付回调验签先验签再改状态用户在小程序端调起微信支付支付结果不是小程序端通知后台的而是微信服务器直接请求统一下单时填写的notify_url。这个回调接口是整套源码里最容易被改坏的地方。/** * 微信支付回调 */ PostMapping(/notify) public String wxPayNotify(RequestBody String xmlData) { // 第一步验签确认请求确实来自微信 WxPayService wxPayService ...; if (!wxPayService.verifyNotify(xmlData)) { return xmlreturn_codeFAIL/return_codereturn_msg签名错误/return_msg/xml; } // 第二步解析回调拿到商户订单号、支付金额 MapString, String result wxPayService.parseNotify(xmlData); String outTradeNo result.get(out_trade_no); String totalFee result.get(total_fee); // 第三步查订单比对金额一致后更新状态 Order order orderMapper.selectByOrderNo(outTradeNo); if (order ! null order.getStatus() 0 totalFee.equals(order.getPayAmount().toString())) { orderMapper.updateStatus(outTradeNo, 1); } // 第四步通知微信处理成功避免微信持续重试 return xmlreturn_codeSUCCESS/return_codereturn_msgOK/return_msg/xml; }给微信返回的报文格式必须固定return_code不带SUCCESS微信就会一直重试。代码里已经限定订单当前状态为待支付才更新这是一道天然的重复回调防线。金额比对也不能省回调里的total_fee必须和订单里的应付金额完全一致再改状态防止金额被篡改。4.3 幂等处理同一个回调收到两次怎么办支付成功通知、退款结果通知这类回调微信官方策略是不保证只通知一次。网络抖动、后台重启都可能导致同一个out_trade_no被通知两次。如果代码不做幂等第二次回调时订单可能已经进入发货环节状态被倒回去或者账户余额被二次加总。我的做法是在订单表设计上留一道约束给out_trade_no建唯一索引同时用一张order_pay_log表记录每一笔支付通知。插入前先查流水号是否已存在重复回调直接返回SUCCESS给微信不进入后续业务逻辑。订单状态字段配合使用每个状态流转都判断前置状态比如只有待支付才能变成已支付已支付才能变成已发货。并发场景下配合乐观锁version字段谁先提交成功谁生效。4.4 订单超时关闭定时扫描与库存回补待支付订单超过15分钟或30分钟未支付要自动关闭并回补库存。这套逻辑一般落在后台定时任务里Spring Boot里就是Scheduled注解加cron表达式Scheduled(cron 0 */5 * * * ?) // 每5分钟扫一次 public void closeTimeoutOrders() { // 查所有超过30分钟仍为待支付的订单 ListOrder list orderMapper.selectTimeoutOrders(30); for (Order order : list) { // 带条件的UPDATE关单避免多节点重复处理 int rows orderMapper.closeOrder(order.getOrderNo()); if (rows 1) { // 关单成功后再回补库存 skuMapper.restoreStock(order.getOrderNo()); } } }cron表达式从左到右是秒、分、时、日、月、周0 */5 * * * ?表示每5分钟的第0秒执行。扫描间隔不是越短越好30分钟超时配5分钟扫描完全够用太频繁只会增加数据库压力。多实例部署时还要注意同一个定时任务只能有一个节点真正执行常见做法是加Redis分布式锁否则关单逻辑会被同时跑好几遍。5. 避坑清单从登录报错到列表加载更多5.1 登录接口报错code被用了两次现象新用户首次进入页面登录接口返回token紧接着请求商品列表却提示登录失效。原因wx.login返回的code只能用一次前端在页面生命周期里把同一个code发了两遍。有些源码在app.js的onLaunch里触发登录同时某个页面的onShow里又触发一次两个请求并发发出后到的那次必然失败。解决登录方法里加状态锁登录进行中直接丢弃后续触发请求封装里对401做统一处理不要在业务代码里到处写wx.login。后台日志要打印调用微信接口的原始返回看到invalid code就能直接定位是重复使用。5.2 微信小程序报10002这类编号错误先查后台原始日志现象真机调试时弹窗提示请求失败错误码类似10002小程序端看不到具体描述。原因这类编号错误多数来自微信服务端和登录态、接口调用频率、参数校验相关不同场景含义不同前端只能拿到一个统一笼统的提示。解决在登录、支付、订阅消息这几个环节的catch里把微信返回的原始errMsg打到服务端日志然后拿着报错时的code、订单号去对照微信侧的返回内容。本地调试用抓包工具看小程序发出的真实请求和响应有帮助但服务端日志才是最终判断依据。5.3 列表加载更多分页参数看着对数据就是不出现象首页滚动到底部onReachBottom触发了好几次新数据没加载出来或者第二页内容和第一页重复。原因page起始值约定不一致。后端按LIMIT (page-1)*size解析前端却传0两边查的是同一页还有的源码把hasMore写成list.length total少了乘page导致永远加载不完。解决统一page从1开始hasMore page * size total。// 页面中下拉加载更多 onReachBottom() { if (!this.data.hasMore || this.data.loading) return const page this.data.page 1 request(/goods/list, GET, { page, size: 10 }) .then(res { this.setData({ list: this.data.list.concat(res.data.list), page, hasMore: page * 10 res.data.total }) }) }loading标志位必须在请求完成后复位否则快速滚动时多次触发回调会重复请求同一页数据。5.4 顶部导航栏高度机型一换就错位现象开发者工具里页面正常真机上标题栏压住了胶囊按钮或自定义导航栏和右上角胶囊重叠。原因自定义导航栏时把navigationBarHeight写死成了固定值但不同机型状态栏高度差异很大刘海屏和普通屏能差几十像素。解决用wx.getWindowInfo()获取状态栏高度用wx.getMenuButtonBoundingClientRect()获取胶囊按钮位置动态计算导航栏高度。旧版本基础库没有getWindowInfo时回退到wx.getSystemInfoSync()调用前做能力判断。这个坑在商城源码里特别常见因为很多页面都用自定义导航栏来腾出商品展示空间。5.5 手机真机全挂模拟器却正常现象开发者工具里一切正常手机预览打开白屏所有请求失败。原因最常见是baseUrl写成了localhost手机访问localhost指向的是手机自己另外后台监听的是127.0.0.1电脑外的设备根本访问不到。解决baseUrl改成电脑局域网IP后台启动参数加上--server.address0.0.0.0防火墙放行对应端口。排查顺序有讲究先看后台日志有没有请求进来没收到是网络不通收到了就继续查接口报错。按这个顺序排查能省下大半天时间。6. 上线前的心跳检查压测、慢SQL和一套能落地的验证顺序真正决定这套源码能不能撑住线上运营的是上线前那几个小时的验证顺序。我习惯按“压测→慢查→回归”三步走。第一步是压测。挑用户量最大的3个接口首页商品列表、商品详情、提交订单。用ApacheBench跑一轮命令是ab -n 1000 -c 50 -k https://api.example.com/api/goods/list1000次请求、50并发看失败率和平均响应时间。失败率超过5%就要回头查代码。订单接口还要额外模拟并发下单验证库存扣减逻辑在压力下是否真的挡住了超卖。第二步是慢SQL排查。上线前开启MySQL慢查询日志跑一轮冒烟测试之后把慢查询记录捞出来看有没有全表扫描。商城最常见的慢查询是订单列表按用户维度查询时没建复合索引在order表上加(user_id, status)联合索引效果立竿见影。第三步是回归验证清单。把开发者工具的“不校验合法域名”关掉换成正式HTTPS域名走一遍注册、登录、加购、下单、支付、退款、售后全流程。支付不能只在模拟器里测模拟器和真机触发的回调路径有差异至少要在安卓真机上走一单1分钱下单到支付成功再退款。我自己踩过最大的坑是上线当天把Tomcat工作线程数调得过大反而把数据库连接池打满整个后台响应成了黑匣子。现在我的原则是线程数宁小勿大配合连接池上限一起配置宁可排队也不要让数据库被压垮。这套JAVA微信小程序商城源码的方向花一周时间吃透订单和支付两条链路后续所有二次开发都会顺很多。希望帮到你。本文还有配套的精品资源点击获取