ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

号卡分销系统源码实战:从代理层级、佣金结算到提现审核的完整落地

号卡分销系统源码实战:从代理层级、佣金结算到提现审核的完整落地 简介这是一套面向号卡分销创业者、代理商及中小型流量卡运营团队的多功能推广分销管理系统源码基于PHP开发主打多接口聚合与三级代理体系可帮助用户快速搭建属于自己的号卡推广与分销平台解决传统分销流程繁琐、代理管理混乱的问题。压缩包共约2000个文件整体96.59MB以635个js脚本、235个php程序、147个scss与99个css样式、206个png及98个jpg图片资源为主另含sql数据库、config配置、functions函数库与字体图标等结构完整、便于二次开发。系统支持运营商等多第三方接口汇聚、无限三级代理、全站双色主题自定义与手机电脑自适应并附带自动安装向导部署门槛较低。目前已有223人学习下载适合希望低成本切入号卡分销赛道、需要一套开箱即用且可灵活扩展的PHP建站方案的开发者与运营者参考使用。1. 号卡分销系统到底在解决什么问题从一张流量卡的结算链路说起上个月有个做通信代理的朋友找我说他手里压着三百多张流量卡每天靠微信群发海报、手工记单、月底拿计算器对佣金结果上个月跟上游对账差了四千多块查了三天也没查明白是哪一单出的问题。这个场景其实特别典型——流量卡推广分销这门生意前端是获客后端是结算中间夹着订单归属、佣金比例、层级分润、提现审核一大堆事纯手工根本扛不住量。所谓多功能号卡推广分销管理系统本质就是把「谁推的卡、卡卖给了谁、这单该给谁多少钱、钱什么时候能提出来」这条链路用一套后台管起来再配一个前端站点让代理能自己看数据、自己提现。它适合两类人一类是手里有代理团队、需要一套系统来管人管账的号卡渠道商另一类是拿到网站源码想自己搭一套跑起来的开发者。这一章先把这门生意的账算清楚后面几章再讲怎么用源码把它落地。2. 拆解号卡分销系统的四层架构代理、订单、佣金、提现怎么串起来拿到一套流量卡推广分销网站源码第一件事不是急着部署而是先把它的数据模型看懂。号卡分销和普通电商最大的区别在于商品是虚拟的号卡资源交易的核心不是发货而是归属和分润。所以整套系统的骨架基本围绕四张核心表转代理表、号卡/套餐表、订单表、佣金流水表。把这四张表的关系理顺后面改代码、加功能、排查对账问题都会顺很多。2.1 代理层级与邀请关系分销树的存储方式分销系统的根是代理关系。常见的做法是每个代理记录一个parent_id上级代理 ID和一个invite_code专属邀请码用户注册时带上邀请码系统就把他挂到对应上级下面形成一棵分销树。这里有个关键选型是只做一级分销还是做多级分销。一级分销逻辑简单A 推的卡佣金只给 A多级分销则是 A 推的卡A 拿大头A 的上级也拿一点。多数号卡系统用的是「一级为主 可选二级返点」的混合模式因为纯多级容易踩到合规红线也不好对账。存储上如果层级不深一般不超过三级直接用parent_id自关联就够了查上级用递归或者一次性把路径缓存成path字段比如1/5/23这样查某个代理的所有下级只需要WHERE path LIKE 1/5/%比递归查询快得多。下面是一个典型的代理表结构CREATE TABLE agent ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, parent_id INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 上级代理ID0为顶级, path VARCHAR(255) NOT NULL DEFAULT COMMENT 层级路径如 1/5/23, invite_code VARCHAR(16) NOT NULL COMMENT 专属邀请码, level TINYINT NOT NULL DEFAULT 1 COMMENT 代理等级, commission_rate DECIMAL(5,4) NOT NULL DEFAULT 0.0000 COMMENT 默认佣金比例, balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 可提现余额, frozen DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 冻结中金额, PRIMARY KEY (id), UNIQUE KEY uk_invite (invite_code), KEY idx_path (path) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;path字段是这套设计的核心它把树形结构拍扁成字符串前缀牺牲一点写入时的维护成本新增代理要拼一次 path换来查询时的极大便利。commission_rate放在代理表上而不是写死在代码里是为了让不同等级的代理能有不同佣金比例运营时改这一个字段就行不用发版。balance和frozen分开存是血泪经验——用户下单后佣金不能立刻可提现得等订单过了售后期才从 frozen 转到 balance否则退款时钱已经提走了你就得自己垫。2.2 订单归属与佣金计算一次下单要写几张表订单进来的时候系统要做三件事记录订单本身、确定这单归哪个代理、按比例算出佣金并写流水。这三件事必须在一个数据库事务里完成否则会出现「订单有了但佣金没算」或者「佣金算了但订单回滚了」的脏数据。下面是一段典型的佣金结算逻辑def create_order_and_commission(user_id, card_id, amount): with db.transaction(): # 1. 找到下单用户绑定的代理 agent db.query_one( SELECT id, path, commission_rate FROM agent WHERE id (SELECT agent_id FROM user WHERE id %s), (user_id,) ) if not agent: raise BizError(该用户没有绑定代理无法结算) # 2. 写订单 order_id db.insert( INSERT INTO orders (user_id, card_id, amount, agent_id, status) VALUES (%s, %s, %s, %s, paid), (user_id, card_id, amount, agent[id]) ) # 3. 算一级佣金写入冻结 commission round(amount * float(agent[commission_rate]), 2) db.insert( INSERT INTO commission_log (agent_id, order_id, amount, type, status) VALUES (%s, %s, %s, first, frozen), (agent[id], order_id, commission) ) db.execute( UPDATE agent SET frozen frozen %s WHERE id %s, (commission, agent[id]) ) # 4. 如果有二级返点沿 path 往上找一级 parent_id get_parent_from_path(agent[path]) if parent_id: parent_rate get_second_rate(parent_id) second_commission round(amount * parent_rate, 2) db.insert( INSERT INTO commission_log (agent_id, order_id, amount, type, status) VALUES (%s, %s, %s, second, frozen), (parent_id, order_id, second_commission) ) db.execute( UPDATE agent SET frozen frozen %s WHERE id %s, (second_commission, parent_id) ) return order_id这段代码有几个参数值得说清楚。commission_rate是小数形式0.15 表示 15%存的时候用DECIMAL(5,4)避免浮点误差算的时候用round(..., 2)保留两位。type字段区分一级和二级佣金方便后面按类型统计。最关键的是statusfrozen——佣金先冻结等订单过了退款期号卡类一般是 7 到 15 天再转成可提现。这个「冻结期」是号卡分销里最容易翻车的地方很多人图省事直接给可提现余额结果用户退款时佣金已经提走只能自己贴钱。2.3 提现审核与资金流水钱出去之前必须留痕提现是资金链路的最后一环也是最需要留痕的一环。常见做法是代理发起提现申请系统冻结对应金额运营在后台审核通过后打款打款完成再扣减冻结金额。整个流程要写三张表提现申请表、资金流水表、代理余额表。提现申请表的status一般有pending待审核、approved已通过待打款、paid已打款、rejected已驳回四个状态。每次状态变更都要往资金流水表插一条记录这样月底对账时任何一笔钱的来龙去脉都能查出来。提示提现审核一定要做「先冻结再打款」不要先打款再扣余额。前者最坏情况是钱没打出去但余额冻着后者最坏情况是余额不够扣导致账目对不上。3. 用 Vue3 后端接口把分销后台跑起来环境、接口、页面三步走现在后台管理系统主流是前后端分离前端用vue3后台管理系统那套Vue3 Vite Element Plus 或 Ant Design Vue后端用 SpringBoot 或者 Node 都行。这套号卡分销源码一般也是这个结构。这一章讲怎么把它从压缩包跑到能登录、能看订单、能审核提现。3.1 环境准备与依赖安装Node 版本和数据库版本别踩错拿到源码先看根目录的README和package.json确认 Node 版本要求。Vue3 Vite 的项目一般要求 Node 16 以上Node 18 更稳。数据库多数用 MySQL 5.7 或 8.0注意 8.0 默认的caching_sha2_password认证方式有些老驱动连不上如果后端报认证错误把用户改成mysql_native_password就行。# 前端依赖安装建议用 pnpm比 npm 快且省磁盘 cd frontend pnpm install # 后端如果是 SpringBoot用 Maven 拉依赖 cd ../backend mvn clean package -DskipTests # 建库并导入初始数据 mysql -u root -p -e CREATE DATABASE card_dist DEFAULT CHARSET utf8mb4; mysql -u root -p card_dist sql/init.sqlpnpm install如果卡在某个包上多半是网络问题可以配国内镜像源。mvn package跳过测试是为了先跑起来等环境通了再回头跑测试。init.sql里一般包含建表语句和一条管理员初始账号导入后先别急着改密码用初始账号登进去看看功能全不全。3.2 核心接口联调登录、订单列表、提现审核三个必通接口前端跑起来后最先要打通的是三个接口登录、订单列表、提现审核。这三个通了说明前后端联调和权限体系没问题。登录接口一般返回一个 token前端存到 localStorage后续请求带在 header 里。订单列表接口要支持分页和按代理筛选提现审核接口要支持通过和驳回两个动作。// 前端请求封装示例统一处理 token 和错误 import axios from axios const request axios.create({ baseURL: import.meta.env.VITE_API_BASE, timeout: 10000 }) // 请求拦截带上 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截统一处理业务错误码 request.interceptors.response.use( res { if (res.data.code ! 0) { // 401 表示登录过期跳回登录页 if (res.data.code 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(new Error(res.data.msg)) } return res.data.data }, err Promise.reject(err) ) export default requestVITE_API_BASE是环境变量开发环境指向本地后端生产环境指向正式域名。code ! 0是常见的业务错误约定具体值看源码里的定义。401 单独处理是因为 token 过期是高频场景统一跳登录比每个页面单独判断省事。联调时如果订单列表返回空先查数据库里有没有数据再查接口的筛选条件是不是把当前代理过滤掉了——权限过滤写错导致「看不到自己的单」是新手最常踩的坑。3.3 页面权限与菜单配置不同等级代理看到不同菜单分销后台的菜单不能所有人一样。顶级管理员能看到所有代理、所有订单、提现审核普通代理只能看到自己的订单和佣金。常见做法是后端返回当前用户的角色和权限码前端根据权限码动态生成路由和菜单。Vue3 里可以用router.addRoute动态添加菜单则用一个递归组件根据权限树渲染。角色可见菜单数据范围超级管理员全部全部代理与订单运营订单、提现审核、号卡管理全部订单无代理管理一级代理我的订单、我的佣金、提现仅自己及下级普通代理我的订单、我的佣金仅自己这张表是权限设计的起点具体到代码里就是每个接口都要做数据范围校验不能只靠前端隐藏菜单。前端隐藏只是体验后端不校验就是漏洞——代理改个请求参数就能看到别人的订单这在号卡分销里是致命的。4. 号卡分销系统避坑清单对账差钱、佣金算错、提现重复的排查记录这一章是我自己踩过和帮别人排查过的坑按「现象 → 原因 → 解决」写每条都是真金白银换来的。4.1 月底对账总是差几百块浮点数和并发更新现象月底对账系统里的佣金总额和手工算的差几百块且差额不固定。原因两个问题叠加。一是佣金计算用了float或double累加多次后出现精度误差二是高并发下多个订单同时更新同一个代理的balance用了UPDATE agent SET balance balance x这种写法本身没问题但如果先SELECT出来在代码里加再UPDATE回去就会丢更新。解决金额字段一律用DECIMAL计算时用 Python 的Decimal或 Java 的BigDecimal。余额更新统一用UPDATE ... SET balance balance ?的原子写法不要读出来再写回去。对账时如果还差查commission_log表里有没有status卡在中间态的记录。4.2 佣金比例改了但老订单跟着变快照没存现象运营把某代理的佣金比例从 15% 调到 12%结果之前已经结算的订单佣金也跟着变了。原因佣金计算时实时读代理表的commission_rate没有在订单生成时把当时的比例存下来。代理比例一改历史订单重算就全乱了。解决在订单表或佣金流水表里加一个rate_snapshot字段下单时把当时的比例写进去后续所有计算和展示都用这个快照值不再回查代理表。这是分销系统的铁律——任何会影响金额的参数下单时必须快照。4.3 提现审核通过后余额没扣事务漏了现象运营点了「审核通过」提现状态变成已通过但代理的冻结余额没减少导致同一笔钱能提两次。原因审核接口只更新了提现申请表的状态忘了在同一个事务里扣减代理的frozen字段。或者扣了但没放在事务里中间报错回滚了状态更新却没回滚余额。解决把「改提现状态」和「扣冻结余额」放进同一个数据库事务任何一步失败整体回滚。同时给提现申请表加唯一约束防止同一笔申请被重复提交审核。4.4 代理看不到自己的下级订单path 前缀查询写错现象一级代理登录后下级代理的订单一条都看不到。原因查询下级用了WHERE path LIKE 1/5/%但顶级代理的 path 是1下级的 path 是1/5LIKE 1/5/%匹配不到1/5本身只匹配1/5/开头的。如果下级直接挂在顶级下面path 是1/5就漏了。解决查询条件改成WHERE path 1/5 OR path LIKE 1/5/%或者统一 path 格式每个 path 结尾都带/查询用LIKE 1/5/%就能覆盖自身。我一般选后者格式统一后不容易出错。4.5 号卡库存超卖下单没锁库存现象某个套餐显示还剩 10 张结果同时进来 20 个订单全部下单成功实际库存只有 10。原因下单时先查库存再扣减两步之间没有锁并发下都查到有库存就都下单了。解决用UPDATE card_stock SET stock stock - 1 WHERE card_id ? AND stock 0根据影响行数判断是否扣减成功返回 0 行说明库存不足直接拒绝下单。这是最经典的乐观锁写法比SELECT ... FOR UPDATE性能好号卡这种秒杀场景够用。5. 让分销系统跑得更稳的两个进阶技巧佣金快照表和异步结算系统能跑起来只是第一步要扛住真实业务量还得在结算链路上做优化。这一章讲两个我实际用过的技巧一个是数据层面的佣金快照表一个是架构层面的异步结算。先说佣金快照表。前面提过下单时要快照佣金比例但光快照比例还不够。真实业务里佣金规则可能更复杂——不同号卡套餐佣金不同、不同等级代理比例不同、活动期间还有额外奖励。如果每次展示佣金都实时算逻辑会散落在各处改一个规则要动好几个地方。我的做法是单独建一张commission_snapshot表下单时把「这单该给谁、给多少、按什么规则算的」整条记录写进去后续所有展示、对账、提现都读这张表不再实时计算。CREATE TABLE commission_snapshot ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, order_id BIGINT UNSIGNED NOT NULL, agent_id INT UNSIGNED NOT NULL, level TINYINT NOT NULL COMMENT 1一级 2二级, base_amount DECIMAL(12,2) NOT NULL COMMENT 计佣基数, rate DECIMAL(5,4) NOT NULL COMMENT 当时比例, amount DECIMAL(12,2) NOT NULL COMMENT 佣金金额, rule_id INT UNSIGNED DEFAULT NULL COMMENT 命中的规则ID, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_order (order_id), KEY idx_agent (agent_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这张表的好处是「不可变」——一旦写入就不再修改退款时插一条负数记录冲抵而不是去改原记录。这样任何时间点的账都能还原审计也方便。rule_id指向规则配置表规则改了也不影响历史快照。再说异步结算。号卡分销的订单量可能很大如果每单都在主流程里同步算佣金、更新余额下单接口会越来越慢。我的做法是把佣金结算拆成异步下单只写订单和一条「待结算」消息到队列Redis List 或 RabbitMQ 都行后台起一个消费者慢慢算。这样下单接口只做最轻的事结算慢一点没关系反正佣金本来就有冻结期。# 下单时只投递消息不同步算佣金 def create_order(user_id, card_id, amount): order_id db.insert( INSERT INTO orders (user_id, card_id, amount, status) VALUES (%s, %s, %s, paid), (user_id, card_id, amount) ) # 投递到 Redis 队列消费者异步处理 redis.lpush(commission_queue, json.dumps({ order_id: order_id, user_id: user_id, amount: str(amount) # Decimal 转字符串避免精度丢失 })) return order_id消费者那边做幂等——同一个order_id处理多次只算一次用commission_snapshot表的唯一索引兜底。异步化之后要注意监控队列积压积压太多说明消费者处理不过来得加消费者或者优化结算逻辑。验证这套方案有没有效果我一般看两个指标一是下单接口的 P99 耗时异步化后应该稳定在几十毫秒二是对账差异率快照表上线后应该趋近于零。如果对账还差优先查快照表里有没有amount为空的记录那说明某条规则没命中得回去补规则配置。最后说个我自己的习惯每次改结算相关代码我都会先在测试库跑一遍「造 1000 单 随机退款 随机提现」的脚本跑完对一遍总账。这个脚本花不了多少时间但能挡住绝大多数结算 bug。号卡分销这行钱的事没有小事宁可上线前多跑几遍也别等代理找上门说佣金少了。希望帮到你。本文还有配套的精品资源点击获取
返回列表