一、前言
私域团购系统的核心不是商城页面,而是背后的五级定价引擎 和自动分佣结算系统。很多开发者第一次接触这类项目时,容易把注意力放在前端展示上,结果佣金算错、层级超限、对账不平,后期修修补补非常痛苦。
本文从实战角度,基于 Spring Boot + MyBatis-Plus + Redis + RocketMQ,讲清楚五级定价、自动分佣、三级合规的技术实现。文中数据来自公开媒体报道,仅供参考,不构成收益承诺。
二、核心表结构设计
2.1 用户身份与关系表
sql
CREATE TABLE `user_identity` (
`user_id` bigint NOT NULL COMMENT '用户ID',
`level` tinyint NOT NULL DEFAULT 1 COMMENT '身份等级 1-5',
`parent_id` bigint DEFAULT NULL COMMENT '直接上级',
`grandparent_id` bigint DEFAULT NULL COMMENT '上上级',
`path` varchar(255) DEFAULT NULL COMMENT '层级路径,如 1/10/25',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
KEY `idx_parent` (`parent_id`),
KEY `idx_grand` (`grandparent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户身份与层级关系';
设计要点:只存直接上级和上上级,第三级通过查询上上级的上级获得。这样既满足三级分销,又避免无限层级查询。
2.2 商品五级价格表
sql
CREATE TABLE `product_price` (
`id` bigint NOT NULL AUTO_INCREMENT,
`product_id` bigint NOT NULL,
`base_price` decimal(10,2) NOT NULL COMMENT '基准零售价',
`level_1_price` decimal(10,2) NOT NULL COMMENT '团购用户价',
`level_2_price` decimal(10,2) NOT NULL COMMENT '分销商价',
`level_3_price` decimal(10,2) NOT NULL COMMENT '小批发价',
`level_4_price` decimal(10,2) NOT NULL COMMENT '中批发价',
`level_5_price` decimal(10,2) NOT NULL COMMENT '大批发价',
`status` tinyint NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_product` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品五级价格';
2.3 订单表
sql
CREATE TABLE `order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(64) NOT NULL,
`user_id` bigint NOT NULL COMMENT '下单用户',
`distributor_id` bigint NOT NULL COMMENT '归属分销商',
`product_id` bigint NOT NULL,
`quantity` int NOT NULL,
`unit_price` decimal(10,2) NOT NULL COMMENT '实际成交单价',
`total_amount` decimal(10,2) NOT NULL,
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2已发货 3已签收 4已完成',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`signed_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_distributor` (`distributor_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
2.4 佣金流水表
sql
CREATE TABLE `commission_flow` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(64) NOT NULL,
`user_id` bigint NOT NULL,
`commission_type` tinyint NOT NULL COMMENT '1零售差 2层级差 3团队收益 4奖励',
`amount` decimal(10,2) NOT NULL,
`status` tinyint NOT NULL DEFAULT 0 COMMENT '0待结算 1已结算 2已失效',
`settled_at` datetime DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_user_type` (`order_no`,`user_id`,`commission_type`),
KEY `idx_user` (`user_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='佣金流水';
唯一键 uk_order_user_type 是幂等的关键,防止同一订单同一用户同一佣金类型重复入账。
三、五级定价引擎实现
3.1 价格查询服务
java
@Service
public class PricingService {
@Autowired
private UserIdentityMapper userIdentityMapper;
@Autowired
private ProductPriceMapper productPriceMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String PRICE_CACHE_KEY = "price:product:";
public BigDecimal getPrice(Long userId, Long productId) {
// 1. 查用户身份
UserIdentity identity = userIdentityMapper.selectById(userId);
if (identity == null) {
throw new BizException("用户身份不存在");
}
// 2. 查商品价格(优先走缓存)
String cacheKey = PRICE_CACHE_KEY + productId;
ProductPrice price = (ProductPrice) redisTemplate.opsForValue().get(cacheKey);
if (price == null) {
price = productPriceMapper.selectByProductId(productId);
if (price == null) {
throw new BizException("商品价格未配置");
}
redisTemplate.opsForValue().set(cacheKey, price, 10, TimeUnit.MINUTES);
}
// 3. 按身份等级返回对应价格
switch (identity.getLevel()) {
case 1: return price.getLevel1Price();
case 2: return price.getLevel2Price();
case 3: return price.getLevel3Price();
case 4: return price.getLevel4Price();
case 5: return price.getLevel5Price();
default: throw new BizException("未知身份等级");
}
}
}
3.2 价格缓存更新
商品价格变更时,删除缓存:
java
public void updateProductPrice(ProductPrice price) {
productPriceMapper.updateById(price);
redisTemplate.delete(PRICE_CACHE_KEY + price.getProductId());
}
四、自动分佣引擎实现
4.1 订单签收触发分佣
订单签收后,发送 MQ 消息,异步计算佣金:
java
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void signOrder(String orderNo) {
Order order = orderMapper.selectByOrderNo(orderNo);
if (order.getStatus() != OrderStatus.SIGNED.getCode()) {
throw new BizException("订单状态不正确");
}
// 发送分佣消息
rocketMQTemplate.convertAndSend("commission-topic", orderNo);
}
}
4.2 分佣消费者
java
@Component
@RocketMQMessageListener(topic = "commission-topic", consumerGroup = "commission-consumer")
public class CommissionConsumer implements RocketMQListener<String> {
@Autowired
private CommissionService commissionService;
@Override
public void onMessage(String orderNo) {
commissionService.calculateAndSave(orderNo);
}
}
4.3 佣金计算核心逻辑
java
@Service
public class CommissionService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserIdentityMapper userIdentityMapper;
@Autowired
private PricingService pricingService;
@Autowired
private CommissionFlowMapper commissionFlowMapper;
@Transactional(rollbackFor = Exception.class)
public void calculateAndSave(String orderNo) {
Order order = orderMapper.selectByOrderNo(orderNo);
if (order == null || order.getStatus() != OrderStatus.SIGNED.getCode()) {
return;
}
Long distributorId = order.getDistributorId();
Long productId = order.getProductId();
Integer quantity = order.getQuantity();
BigDecimal dealPrice = order.getUnitPrice();
// 1. 零售差价:分销商本人
BigDecimal distributorPrice = pricingService.getPrice(distributorId, productId);
BigDecimal retailDiff = dealPrice.subtract(distributorPrice)
.multiply(BigDecimal.valueOf(quantity));
saveCommission(orderNo, distributorId, CommissionType.RETAIL, retailDiff);
// 2. 层级差价:直接上级、上上级,最多两级
UserIdentity distributor = userIdentityMapper.selectById(distributorId);
Long parentId = distributor.getParentId();
if (parentId != null) {
BigDecimal parentPrice = pricingService.getPrice(parentId, productId);
BigDecimal parentDiff = distributorPrice.subtract(parentPrice)
.multiply(BigDecimal.valueOf(quantity));
saveCommission(orderNo, parentId, CommissionType.SPREAD, parentDiff);
UserIdentity parent = userIdentityMapper.selectById(parentId);
Long grandId = parent.getParentId();
if (grandId != null) {
BigDecimal grandPrice = pricingService.getPrice(grandId, productId);
BigDecimal grandDiff = parentPrice.subtract(grandPrice)
.multiply(BigDecimal.valueOf(quantity));
saveCommission(orderNo, grandId, CommissionType.SPREAD, grandDiff);
}
}
// 3. 团队管理收益:按直属团队真实销售额计算,三级内
// 此处省略具体实现,核心是统计真实成交订单,不统计人头
// ...
}
private void saveCommission(String orderNo, Long userId, CommissionType type, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
return;
}
CommissionFlow flow = new CommissionFlow();
flow.setOrderNo(orderNo);
flow.setUserId(userId);
flow.setCommissionType(type.getCode());
flow.setAmount(amount);
flow.setStatus(CommissionStatus.PENDING.getCode());
try {
commissionFlowMapper.insert(flow);
} catch (DuplicateKeyException e) {
// 幂等:已存在则忽略
log.warn("佣金流水已存在,orderNo={}, userId={}, type={}", orderNo, userId, type);
}
}
}
4.4 幂等与并发控制
-
数据库唯一键 :
uk_order_user_type保证同一订单同一用户同一类型只入账一次。 -
MQ 消费幂等 :消费者先查是否已处理,或依赖数据库唯一键捕获
DuplicateKeyException。 -
分布式锁 :如果同一订单并发触发多次,可用 Redis 锁
lock:commission:orderNo控制。
java
public void calculateWithLock(String orderNo) {
String lockKey = "lock:commission:" + orderNo;
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(locked)) {
return;
}
try {
calculateAndSave(orderNo);
} finally {
redisTemplate.delete(lockKey);
}
}
五、三级合规的技术实现
5.1 层级上限锁定
绑定上下级时校验深度:
java
@Service
public class RelationService {
private static final int MAX_LEVEL = 3;
@Autowired
private UserIdentityMapper userIdentityMapper;
public void bindParent(Long userId, Long parentId) {
if (userId.equals(parentId)) {
throw new BizException("不能绑定自己");
}
int parentDepth = getDepth(parentId);
if (parentDepth >= MAX_LEVEL) {
throw new BizException("分销层级已达上限");
}
UserIdentity identity = new UserIdentity();
identity.setUserId(userId);
identity.setParentId(parentId);
identity.setGrandparentId(getParentId(parentId));
userIdentityMapper.updateById(identity);
}
private int getDepth(Long userId) {
int depth = 1;
Long current = userId;
while (depth <= MAX_LEVEL) {
UserIdentity identity = userIdentityMapper.selectById(current);
if (identity == null || identity.getParentId() == null) {
break;
}
current = identity.getParentId();
depth++;
}
return depth;
}
private Long getParentId(Long userId) {
UserIdentity identity = userIdentityMapper.selectById(userId);
return identity == null ? null : identity.getParentId();
}
}
5.2 按真实销售计酬
佣金计算只读取 order 表中状态为"已签收"的订单,不读取任何"团队人数""拉人头"数据。计酬科目仅包含:零售差价、层级差价(最多两级)、团队销售服务费(按真实销售额)、平级/培育奖励(挂钩真实销售)、团队冲量奖(后台设上限,不挂钩人头)。
5.3 全链路审计日志
sql
CREATE TABLE `audit_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL,
`action` varchar(64) NOT NULL COMMENT '操作类型',
`target_id` bigint DEFAULT NULL,
`before_data` json DEFAULT NULL,
`after_data` json DEFAULT NULL,
`ip` varchar(45) DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
KEY `idx_action` (`action`),
KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审计日志';
关键操作(层级绑定、佣金结算、提现、价格变更)均写入审计日志,保留至少3年,支持按用户、时间、操作类型检索。
六、对账与异常处理
6.1 每日对账
java
@Scheduled(cron = "0 0 2 * * ?")
public void dailyReconciliation() {
// 1. 统计当日已签收订单总金额
// 2. 统计当日佣金流水总金额
// 3. 校验订单金额 - 佣金金额 - 平台留存 = 0
// 4. 异常记录写入对账异常表,人工介入
}
6.2 异常处理策略
-
佣金金额为负:不生成流水,记录日志。
-
层级关系断裂:跳过该级佣金,记录异常。
-
MQ 消费失败:重试3次,仍失败进入死信队列,人工处理。
-
对账不平:冻结相关账户,触发告警。
七、性能优化建议
-
缓存:商品五级价格、用户身份信息放入 Redis,减少数据库压力。
-
异步结算:佣金计算走 MQ,避免阻塞订单主流程。
-
分库分表:订单表和佣金流水表按用户ID哈希分片。
-
批量插入:佣金流水批量写入,减少数据库交互。
-
读写分离:订单查询走从库,佣金计算走主库。
八、总结
本文从实战角度讲解了五级分销定价与自动分佣系统的核心实现:
-
五级定价 :通过
user_identity+product_price联动,按身份等级返回对应价格。 -
自动分佣:订单签收后发 MQ,消费者异步计算零售差、层级差、团队收益,用唯一键和分布式锁保证幂等。
-
三级合规:绑定层级时校验深度,佣金只读真实成交订单,关键操作写审计日志。
我们提供良久团购模式系统完整源码交付,包含五级定价、自动结算、裂变分佣、合规审计等全模块,支持二次开发。
风险提示:本文数据来自公开媒体报道,仅供参考,不构成收益承诺或投资建议。系统采购方应确保经营行为符合当地法律法规和监管要求,不得以入门费、拉人头、多级计酬等方式违规经营。具体模式配置建议咨询专业法律及合规人士。