夜雨聆风学习资料网

ARTICLE · 1358174

JAVA商城系统推三返一商城系统源码支持小程序+公众号+H5

JAVA商城系统推三返一商城系统源码支持小程序+公众号+H5

小程序 · 公众号 · H5

MALL SYSTEM / JAVA

推三返一 · 分享裂变 · 收益明细 · 商城后台

推三返一商城系统

分享成交即记账,三人成团返一单。

SpringBoot · MyBatisPlus · MySQL

uni-app · Vue + ElementUI  /  小程序 · 公众号 · H5

做分销的团队常卡在同一件事上:拉来的人算谁的、成交了该返多少、返的钱什么时候到账——这三件事散在群聊和表格里,运营对不上账,用户也说不清自己还差几单。

这套系统的做法是把链路收进一个后台:分享即绑定,成交即记账,收益可查可提。用户在自己页面能看到邀请进度和每一笔返利,运营在后台看到的是同一份数据。

下面按八个功能点讲清它怎么跑,再给七段关键代码(表结构、下单事务、库存扣减、幂等、权限、多端请求、后台表格)。

OVERVIEW / 功能总览

八个模块,覆盖「卖货 + 裂变 + 结算」

功能点按用户实际走一遍的顺序排列:先看到商品,再下单,下单后进入返利链路,最后在收益页提现。后台负责商品、优惠券与返利审核,两端共用一套订单数据。

热销推荐

首页按销量与价格带出商品,加购一步到位,少翻页。

精准分类

多级分类加首页宫格,两次点击进到目标品类。

推三返一

邀请三人成交返一单,进度条显示还差几单。

分享好友

推广码与海报一键分享,扫码即建立上下级。

收益明细

每笔返利的来源订单、金额、状态都可追溯。

优惠券管理

后台按品类发券,用户端领用后下单抵扣。

地址管理

收货地址增删改与默认地址,下单直接带出。

商品管理

上下架、库存与价格在后台统一维护。

一套主数据

商品、订单、会员共用一套用户与权限

01热销推荐

输入首页热销位按近 7 日销量与价格带取数,支持直接加购。

状态商品按销量倒序,同销量按上架时间;首页宫格与分类页共用同一份数据源。

输出点「+」即加入购物车,角标同步;热销位展示真实成交排序,运营不用每天改配置。

【推三返一商城】实拍 01|首页热销推荐、我的页与商品详情;截图数据为演示数据

02精准分类

输入左侧一级分类,右侧二级分类与商品列表,支持关键词搜索。

状态分类树在后台维护,最多三级;商品挂在末级分类上,父级自动聚合。

输出切换分类时局部刷新不跳页,空分类显示「暂无商品」;改分类不影响已生成订单的商品快照。

03推三返一

输入活动按商品配置:邀请三人成交,返一单等值金额。

状态每笔成交写一条返利流水,状态为冻结;三人成团后转为可提现。

输出活动页显示累计收益、本月预估与邀请人数;返利以实付为基数,退款时同步冲回。

【推三返一商城】实拍 02|订单详情、推三返一进度与购物车;截图数据为演示数据

04分享好友

输入推广码、海报与链接三种分享方式,扫码即绑定关系。

状态绑定关系一人一条,已绑定的不覆盖;同一设备重复扫码不重复计数。

输出邀请人由服务端落库,前端传参可篡改无效;绑定只发生一次,避免归属争议。

05收益明细

输入收益页按「累计 / 本月 / 今日」三个口径统计。

状态流水分邀请奖励、消费返利、签到积分三类,每条带来源订单号。

输出可按时间筛选,点开看到对应订单与结算状态;统计在服务端算好再下发,前端不二次累加。

【推三返一商城】实拍 03|收益明细、确认订单与余额提现;截图数据为演示数据

06优惠券管理

输入后台按品类或全场发券,设置面额、门槛与有效期。

状态券分未使用、已使用、已过期三态,过期由定时任务统一处理。

输出领券中心展示可领券,下单页自动匹配最优券;核销与订单在同一事务内,一张券只核销一次。

07地址管理

输入收货人、手机号、地区与详细地址,支持设为默认。

状态每个用户最多 20 条,默认地址唯一,删除后自动顺延。

输出下单页带出默认地址并可切换;接口按用户维度隔离,越权查询直接拒绝。

08商品管理

输入后台维护商品信息、库存、价格与上下架状态。

状态商品有草稿、上架、下架三态,下架不影响已生成订单。

输出改价改库存留操作日志,库存变更走条件更新防超卖;删除是逻辑删除,历史订单保留快照。

ARCHITECTURE / 三端分工

同一套订单,三端各管一段

端负责什么不该做什么
用户端浏览、加购、下单、看收益、申请提现不算返利金额,不判断谁能提现
管理后台商品、优惠券、返利审核与提现打款不直接改用户余额,只能走审核流
服务端下单事务、返利记账、幂等与权限校验不信任前端传来的金额与邀请人

一句话分工:前端只负责收集与展示,金额、归属、状态全部由服务端算;后台改任何状态都要留痕,方便对账时回看。

CODE / 数据库与核心代码

七段关键代码:从建表到后台表格

按「表结构 → 下单事务 → 库存扣减 → 幂等 → 权限 → 多端请求 → 后台表格」的顺序给,每段都能直接放进项目里改。

以下均为示例实现,生产环境需补充参数校验、权限、日志、监控、限流、支付验签等。

01三张表撑起返利链路

返利能不能对账,取决于有没有把「订单—绑定—流水」三件事各存一张表。

SQL

-- 订单表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下单在同一事务里完成

建单、扣库存、绑关系必须同生共死,否则会出现「扣了库存没订单」。

JAVA

@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 看影响行数。

JAVA

@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幂等三层兜底

用户连点两下、网络重发,都会产生重复订单,只靠前端禁用按钮不够。

JAVA

/** 幂等:请求号查重 -> 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后台操作要能说清谁能做

提现审核和商品上下架是两类风险,权限不能笼统给「管理员」。

JAVA

@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 与重试逻辑就会分叉。

JAVASCRIPT

// 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后台表格不空转

返利列表要能按订单号与状态筛,能审核,能翻页。

VUE

<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 / 合规与上线

做分销,这几条边界要写进产品里

只做一层返利。系统里的返利关系只有「邀请人—被邀请人」一层,不做二级、三级计酬,也不按人头收费。

返利来自真实成交。返利金额以订单实付为基数,退款同步冲回;没有成交就没有返利,不存在「交钱升级」。

提现要走审核。余额只能通过后台审核后打款,用户端不能直接改余额;每笔流水可追溯到来源订单。

数据只给本人看。收益与地址接口按用户维度隔离,越权查询直接拒绝;后台操作全部留操作日志。

上线前建议逐项打勾

☐ 首页热销位能按销量自动排序,加购角标同步☐ 分类最多三级,商品挂在末级分类上☐ 邀请关系一人一条,重复扫码不覆盖☐ 三人成团后返利由冻结转为可提现☐ 退款时对应返利流水同步冲回☐ 库存扣减用条件更新,影响行数为 0 即失败☐ 提现审核只对财务角色开放☐ 后台改价、改库存、改状态都留操作日志

FIT / 适合谁

适合谁用

适合做食品、日用、农特产这类复购商品的团队:客单价不高、决策快,靠老客带新客比投流划算。

不太适合已经有小程序商城、想把「分享返利」做成固定玩法,但不想自己从零写结算逻辑的团队。

二次开发需要源码交付、后续自己二次开发的服务商:表结构与接口都按常规 SpringBoot 分层写,接手成本低。

FAQ / 常见问题

常被问到的五个问题

Q:「推三返一」是三级分销吗?A:不是。系统只记录一层邀请关系,返利也只算这一层,不按层级计酬。

Q:返利什么时候到账?A:订单完成后流水先冻结,达到活动条件(三人成交)转可提现,提现需后台审核。

Q:用户退款了,返利怎么办?A:退款成功时对应返利流水同步冲回,已提现的部分记负数,账目仍能对上。

Q:能同时开多个活动吗?A:可以按商品配置不同活动,同一商品同一时间只生效一个,避免重复返利。

Q:源码包含哪些部分?A:服务端(SpringBoot + MyBatisPlus + MySQL)、后台(Vue + ElementUI)、用户端(uni-app,可编译到小程序、公众号、H5)。

LAST / 写在最后

分销做不起来,多半不是流量问题,而是「谁带来的人、该返多少、什么时候到账」这三件事说不清。

这套系统把这三件事收进同一套表和同一个后台,剩下的就是运营节奏。

需要看完整表结构或跑一遍演示,可以在评论区留言说明你的品类。

文中截图均为演示数据,界面与文案随版本可能调整;具体功能以实际交付版本为准。

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料