ARTICLE · 1358174
JAVA商城系统推三返一商城系统源码支持小程序+公众号+H5
|
做分销的团队常卡在同一件事上:拉来的人算谁的、成交了该返多少、返的钱什么时候到账——这三件事散在群聊和表格里,运营对不上账,用户也说不清自己还差几单。
这套系统的做法是把链路收进一个后台:分享即绑定,成交即记账,收益可查可提。用户在自己页面能看到邀请进度和每一笔返利,运营在后台看到的是同一份数据。
下面按八个功能点讲清它怎么跑,再给七段关键代码(表结构、下单事务、库存扣减、幂等、权限、多端请求、后台表格)。
OVERVIEW / 功能总览
八个模块,覆盖「卖货 + 裂变 + 结算」
功能点按用户实际走一遍的顺序排列:先看到商品,再下单,下单后进入返利链路,最后在收益页提现。后台负责商品、优惠券与返利审核,两端共用一套订单数据。
|
01热销推荐
输入首页热销位按近 7 日销量与价格带取数,支持直接加购。
状态商品按销量倒序,同销量按上架时间;首页宫格与分类页共用同一份数据源。
输出点「+」即加入购物车,角标同步;热销位展示真实成交排序,运营不用每天改配置。

【推三返一商城】实拍 01|首页热销推荐、我的页与商品详情;截图数据为演示数据
02精准分类
输入左侧一级分类,右侧二级分类与商品列表,支持关键词搜索。
状态分类树在后台维护,最多三级;商品挂在末级分类上,父级自动聚合。
输出切换分类时局部刷新不跳页,空分类显示「暂无商品」;改分类不影响已生成订单的商品快照。
03推三返一
输入活动按商品配置:邀请三人成交,返一单等值金额。
状态每笔成交写一条返利流水,状态为冻结;三人成团后转为可提现。
输出活动页显示累计收益、本月预估与邀请人数;返利以实付为基数,退款时同步冲回。

【推三返一商城】实拍 02|订单详情、推三返一进度与购物车;截图数据为演示数据
04分享好友
输入推广码、海报与链接三种分享方式,扫码即绑定关系。
状态绑定关系一人一条,已绑定的不覆盖;同一设备重复扫码不重复计数。
输出邀请人由服务端落库,前端传参可篡改无效;绑定只发生一次,避免归属争议。
05收益明细
输入收益页按「累计 / 本月 / 今日」三个口径统计。
状态流水分邀请奖励、消费返利、签到积分三类,每条带来源订单号。
输出可按时间筛选,点开看到对应订单与结算状态;统计在服务端算好再下发,前端不二次累加。

【推三返一商城】实拍 03|收益明细、确认订单与余额提现;截图数据为演示数据
06优惠券管理
输入后台按品类或全场发券,设置面额、门槛与有效期。
状态券分未使用、已使用、已过期三态,过期由定时任务统一处理。
输出领券中心展示可领券,下单页自动匹配最优券;核销与订单在同一事务内,一张券只核销一次。
07地址管理
输入收货人、手机号、地区与详细地址,支持设为默认。
状态每个用户最多 20 条,默认地址唯一,删除后自动顺延。
输出下单页带出默认地址并可切换;接口按用户维度隔离,越权查询直接拒绝。
08商品管理
输入后台维护商品信息、库存、价格与上下架状态。
状态商品有草稿、上架、下架三态,下架不影响已生成订单。
输出改价改库存留操作日志,库存变更走条件更新防超卖;删除是逻辑删除,历史订单保留快照。
ARCHITECTURE / 三端分工
同一套订单,三端各管一段
|
一句话分工:前端只负责收集与展示,金额、归属、状态全部由服务端算;后台改任何状态都要留痕,方便对账时回看。
CODE / 数据库与核心代码
七段关键代码:从建表到后台表格
按「表结构 → 下单事务 → 库存扣减 → 幂等 → 权限 → 多端请求 → 后台表格」的顺序给,每段都能直接放进项目里改。
以下均为示例实现,生产环境需补充参数校验、权限、日志、监控、限流、支付验签等。
01三张表撑起返利链路
返利能不能对账,取决于有没有把「订单—绑定—流水」三件事各存一张表。
-- 订单表CREATE TABLE t_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, member_id BIGINT NOT NULL, amt DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, request_no VARCHAR(64) NOT NULL, version INT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_no (order_no), UNIQUE KEY uk_req (member_id, request_no), KEY idx_m (member_id, status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 推荐关系:一人只绑一个上级CREATE TABLE t_bind ( id BIGINT PRIMARY KEY AUTO_INCREMENT, member_id BIGINT NOT NULL, inviter_id BIGINT NOT NULL, bind_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_m (member_id), KEY idx_i (inviter_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 返利流水:同单同人只记一条CREATE TABLE t_rebate ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, inviter_id BIGINT NOT NULL, amt DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_ou (order_no, inviter_id), KEY idx_i (inviter_id, status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
订单号与请求号都建唯一键;返利流水用(订单号+推广人)做唯一,天然防重。
02下单在同一事务里完成
建单、扣库存、绑关系必须同生共死,否则会出现「扣了库存没订单」。
@Servicepublic class OrderService { @Resource OrderMapper orderMapper; @Resource BindMapper bindMapper; @Resource StockMapper stockMapper; /** 建单 + 扣库存 + 绑推荐,任一失败全回滚 */ @Transactional(rollbackFor = Exception.class) public Long create(OrderDTO o) { int n = stockMapper.deduct(o.getSkuId(), o.getQty()); if (n == 0) { throw new BizException("库存不足"); } OrderPO p = new OrderPO(); p.setOrderNo(Snowflake.next("SO")); p.setMemberId(o.getMemberId()); p.setAmt(o.getAmt()); p.setRequestNo(o.getRequestNo()); p.setStatus(0); orderMapper.insert(p); String inv = o.getInviterId(); if (inv != null && inv.length() > 0) { bindMapper.bindOnce(o.getMemberId(), Long.parseLong(inv)); } return p.getId(); }} |
库存不足直接抛异常回滚;绑定失败也回滚,避免出现半截数据。
03库存用条件更新扣
先查后改在并发下必然超卖,改成 WHERE stock >= qty 看影响行数。
@Mapperpublic interface StockMapper { /** 库存不足时影响行数为 0 */ @Update("UPDATE t_sku " + "SET stock = stock - #{qty} " + "WHERE id = #{id} AND stock >= #{qty}") int deduct(@Param("id") Long id, @Param("qty") int qty); /** 带版本号:防并发覆盖 */ @Update("UPDATE t_sku " + "SET stock = stock - #{qty}, " + "version = version + 1 " + "WHERE id = #{id} AND stock >= #{qty} " + "AND version = #{ver}") int deductWithVer(@Param("id") Long id, @Param("qty") int qty, @Param("ver") int ver);} |
影响行数为 0 就是库存不够或版本被改,直接提示用户重试。
04幂等三层兜底
用户连点两下、网络重发,都会产生重复订单,只靠前端禁用按钮不够。
/** 幂等:请求号查重 -> Redis 占位 -> 唯一键兜底 */public Long submit(OrderDTO o) { Long uid = o.getMemberId(); String req = o.getRequestNo(); OrderPO old = orderMapper.findByReq(uid, req); if (old != null) { return old.getId(); // 已处理 } String key = "order:req:" + uid + ":" + req; Boolean ok = redis.opsForValue() .setIfAbsent(key, "1", 10, MINUTES); if (Boolean.FALSE.equals(ok)) { throw new BizException("请勿重复提交"); } try { return orderService.create(o); } catch (DuplicateKeyException e) { String m = e.getMessage(); if (m != null && m.contains("uk_req")) { return orderMapper .findByReq(uid, req).getId(); } throw e; // 订单号撞了 }} |
请求号查重挡重复提交,Redis 占位挡并发,唯一键冲突时区分是哪个键撞了。
05后台操作要能说清谁能做
提现审核和商品上下架是两类风险,权限不能笼统给「管理员」。
@RestController@RequestMapping("/admin/rebate")public class RebateAdminController { /** 提现审核:只有财务能改状态 */ @PreAuthorize("hasRole('FINANCE')") @PostMapping("/audit/{id}") public R audit(@PathVariable Long id, @RequestParam Integer pass) { RebatePO r = rebateService.get(id); if (r == null || r.getStatus() != 0) { return R.fail("已处理"); } rebateService.audit(id, pass, currentUser()); return R.ok(); } /** 商品上下架:运营只能动本店 */ @PreAuthorize("hasAnyRole('ADMIN','OP')") @PostMapping("/goods/{id}/status") public R shelf(@PathVariable Long id, @RequestParam Integer st) { GoodsPO g = goodsService.get(id); Long shop = g.getShopId(); if (!perm.canTouch(currentUser(), shop)) { return R.fail("无权操作该店商品"); } goodsService.updateStatus(id, st); return R.ok(); }} |
审核只给财务角色,商品操作限定本门店,越权直接拒绝并留日志。
06多端请求统一封装
小程序、公众号、H5 三端各写一套请求,token 与重试逻辑就会分叉。
// uni-app 统一请求:带 token 与请求号const BASE = 'https://demo.example.com/api';export function req(url, data, method) { method = method || 'POST'; const token = uni.getStorageSync('token'); return new Promise((resolve, reject) => { uni.request({ url: BASE + url, method, data, header: { 'Content-Type': 'application/json', 'Authorization': token || '', 'X-Request-No': reqNo(), 'X-Client': 'mp-weixin' }, success: (res) => { const r = res.data || {}; if (r.code === 0) return resolve(r.data); if (r.code === 401) { uni.removeStorageSync('token'); uni.reLaunch({ url: '/pages/login' }); return reject(new Error('未登录')); } toast(r.msg); reject(new Error(r.msg)); }, fail: (e) => { toast('网络异常'); reject(e); } }); });}function toast(t) { uni.showToast({ title: t, icon: 'none' });}function reqNo() { const r = Math.random().toString(36); return Date.now() + '-' + r.slice(2, 8);} |
一处封装:带 token、带请求号、401 统一跳登录,业务代码只关心数据。
07后台表格不空转
返利列表要能按订单号与状态筛,能审核,能翻页。
<template> <div> <el-input v-model="q.no" placeholder="订单号" clearable /> <el-select v-model="q.status" clearable> <el-option label="冻结中" :value="0" /> <el-option label="已入账" :value="1" /> </el-select> <el-button type="primary" @click="load"> 查询 </el-button> <el-table :data="rows" border size="small"> <el-table-column prop="no" label="订单号" /> <el-table-column prop="user" label="推广人" /> <el-table-column prop="amt" label="返利" /> <el-table-column label="状态"> <template slot-scope="s"> <el-tag :type="tag(s.row.status)"> {{ text(s.row.status) }} </el-tag> </template> </el-table-column> <el-table-column label="操作"> <template slot-scope="s"> <el-button type="text" v-if="!s.row.st" @click="ok(s.row)"> 审核 </el-button> <span v-else>-</span> </template> </el-table-column> </el-table> <el-pagination :total="total" :page-size="10" layout="prev,pager,next" @current-change="onPage" /> </div></template> |
状态用标签区分冻结与入账,操作列只在可审核时出现按钮。
COMPLIANCE / 合规与上线
做分销,这几条边界要写进产品里
只做一层返利。系统里的返利关系只有「邀请人—被邀请人」一层,不做二级、三级计酬,也不按人头收费。
返利来自真实成交。返利金额以订单实付为基数,退款同步冲回;没有成交就没有返利,不存在「交钱升级」。
提现要走审核。余额只能通过后台审核后打款,用户端不能直接改余额;每笔流水可追溯到来源订单。
数据只给本人看。收益与地址接口按用户维度隔离,越权查询直接拒绝;后台操作全部留操作日志。
上线前建议逐项打勾
|
FIT / 适合谁
适合谁用
适合做食品、日用、农特产这类复购商品的团队:客单价不高、决策快,靠老客带新客比投流划算。
不太适合已经有小程序商城、想把「分享返利」做成固定玩法,但不想自己从零写结算逻辑的团队。
二次开发需要源码交付、后续自己二次开发的服务商:表结构与接口都按常规 SpringBoot 分层写,接手成本低。
FAQ / 常见问题
常被问到的五个问题
Q:「推三返一」是三级分销吗?A:不是。系统只记录一层邀请关系,返利也只算这一层,不按层级计酬。
Q:返利什么时候到账?A:订单完成后流水先冻结,达到活动条件(三人成交)转可提现,提现需后台审核。
Q:用户退款了,返利怎么办?A:退款成功时对应返利流水同步冲回,已提现的部分记负数,账目仍能对上。
Q:能同时开多个活动吗?A:可以按商品配置不同活动,同一商品同一时间只生效一个,避免重复返利。
Q:源码包含哪些部分?A:服务端(SpringBoot + MyBatisPlus + MySQL)、后台(Vue + ElementUI)、用户端(uni-app,可编译到小程序、公众号、H5)。
LAST / 写在最后
分销做不起来,多半不是流量问题,而是「谁带来的人、该返多少、什么时候到账」这三件事说不清。
这套系统把这三件事收进同一套表和同一个后台,剩下的就是运营节奏。
需要看完整表结构或跑一遍演示,可以在评论区留言说明你的品类。
文中截图均为演示数据,界面与文案随版本可能调整;具体功能以实际交付版本为准。















































































