
简介这份PHP推广系统源码面向具备一定PHP与MySQL基础的开发者用于搭建和二次开发在线营销推广平台解决广告投放、用户追踪与效果统计等业务需求。压缩包共14个文件约907KB以php脚本为主体涵盖登录注册、配置与业务逻辑等核心页面另含url快捷入口、ec易语言模块、dll皮肤组件、bak备份及txt说明等辅助文件结构紧凑便于快速部署与本地调试。系统围绕用户管理、广告管理、跟踪代码、报表分析、支付结算与API接口等模块展开可查看并修改源码以调整推广算法、接入新广告渠道或集成第三方服务。目前已有1824人学习下载适合希望深入理解推广系统运行机制、进行个性化二次开发的中高级开发者参考借鉴。1. 推广系统源码拆解从渠道追踪到佣金结算一套能跑通的最小闭环如果你做过渠道投放大概率遇到过这种局面广告费花出去了后台显示点击量不错但真正注册、下单的人到底从哪个渠道来的说不清。更麻烦的是如果业务涉及分销或代理月底算佣金时只能靠 Excel 手工对对到凌晨还容易出错。这套推广系统源码要解决的就是这件事——它把渠道链接生成、点击归因、用户绑定、订单分佣、提现审核串成了一条完整链路适合做增长工具的后端开发、需要快速搭一套分销后台的小团队或者想研究归因逻辑的独立开发者。源码是真实可运行的工程结构不是那种只有几个空壳文件的演示包。下面我按实际拆项目的顺序从技术选型一路讲到二次开发时怎么改分佣规则。2. 技术栈与目录结构先看清这套推广系统用了什么、文件怎么摆2.1 后端选型与依赖清单这套系统后端用的是 Python 技术栈Web 框架选的是 Flask数据库层用 SQLAlchemy 做 ORM缓存和点击去重依赖 Redis异步任务用 Celery 处理佣金结算和提现审核。这个组合在中小型推广系统里很常见原因是渠道追踪对响应速度要求高Flask 轻量、启动快配合 Redis 做点击频控足够用而佣金结算涉及批量计算丢给 Celery 异步跑不会阻塞主流程。核心依赖大致如下# requirements.txt 关键依赖 Flask2.3.2 # Web 框架提供路由和请求上下文 SQLAlchemy2.0.19 # ORM映射用户、渠道、订单、佣金四张核心表 Flask-SQLAlchemy3.0.5 redis4.6.0 # 点击去重、渠道链接缓存 celery5.3.1 # 异步结算佣金、提现审核 pymysql1.1.0 # MySQL 驱动这里有个细节值得注意Flask 版本锁在 2.3.xSQLAlchemy 用的是 2.0 的新式查询语法。如果你本地环境里已经装了旧版 SQLAlchemy 1.4直接跑会报Query对象没有filter_by之外的链式方法常见做法是建一个干净的虚拟环境再装依赖别在全局环境里硬升。2.2 目录结构与模块职责源码目录按职责分层不是把所有逻辑塞进一个app.py。我拆的时候先看目录基本就能判断这套代码能不能二次开发promotion-system/ ├── app/ │ ├── __init__.py # 应用工厂注册蓝图和扩展 │ ├── models/ # 数据模型层 │ │ ├── user.py # 用户表含上级绑定关系 │ │ ├── channel.py # 渠道表存推广链接和归属人 │ │ ├── order.py # 订单表记录来源渠道 │ │ └── commission.py # 佣金流水表含结算状态 │ ├── views/ # 路由层 │ │ ├── channel.py # 生成链接、点击跳转 │ │ ├── order.py # 下单回调、归因写入 │ │ └── withdraw.py # 提现申请与审核 │ ├── services/ # 业务逻辑层 │ │ ├── attribution.py # 归因核心点击→注册→下单 │ │ └── settlement.py # 佣金计算与分账 │ └── tasks/ # Celery 异步任务 │ └── settle_task.py ├── config.py # 数据库、Redis、Celery 配置 ├── manage.py # 启动入口和 CLI 命令 └── requirements.txtservices/attribution.py和services/settlement.py是整套系统的核心前者决定「这单算谁的」后者决定「算多少钱」。二次开发时改分佣比例、改归因窗口基本都落在这两个文件里。models/下的表结构设计得比较克制没有过度冗余字段加自定义字段的成本低。2.3 数据表关系速览四张核心表的关系用一句话概括一个用户可以有多个渠道一个渠道产生多笔订单一笔订单对应一条或多条佣金流水。关键字段如下表名关键字段说明userid, parent_id, invite_codeparent_id 记录上级形成分销树channelid, user_id, link_code, click_countlink_code 是推广链接唯一标识orderid, user_id, channel_id, amount, statuschannel_id 记录归因结果commissionid, order_id, user_id, amount, statusstatus 控制待结算/已结算/已提现order.channel_id是归因的落点下单时如果这个字段为空说明归因失败佣金就不会产生。排查问题时第一眼看的就是它。3. 核心链路拆解一次点击到一笔佣金到底经历了什么3.1 推广链接生成与点击归因渠道链接的格式通常是https://域名/track/{link_code}用户点进来先经过views/channel.py的跳转接口。这里做了两件事记录点击、把渠道标识种到 Cookie 或 Redis 里供后续注册和下单时读取。# app/views/channel.py 点击跳转核心逻辑 channel_bp.route(/track/link_code) def track(link_code): channel Channel.query.filter_by(link_codelink_code).first() if not channel: return redirect(/) # 无效链接直接回首页不报错暴露细节 # 点击计数用 Redis 自增避免每次写库 redis_client.incr(fchannel:click:{channel.id}) # 把渠道 ID 写入 Rediskey 用访客标识有效期 7 天 visitor_id request.cookies.get(visitor_id) or str(uuid.uuid4()) redis_client.setex(fvisit:{visitor_id}, 7 * 86400, channel.id) resp redirect(channel.target_url or /) resp.set_cookie(visitor_id, visitor_id, max_age7 * 86400) return resp这段逻辑的关键参数是7 * 86400也就是归因窗口设为 7 天。用户点链接后 7 天内注册并下单都算这个渠道的功劳。改这个值就能调整归因激进程度改成 1 天渠道之间抢单少但可能漏归因改成 30 天归因全但容易把自然流量算到渠道头上。我一般会把它抽到config.py里别硬编码在视图函数里。3.2 注册绑定与订单归因写入用户注册时系统会读 Redis 里的visit:{visitor_id}把渠道归属写到user.parent_id或单独的绑定表。下单回调时再根据用户当前绑定关系把channel_id写进订单。# app/services/attribution.py 归因服务 def bind_channel_on_register(user, visitor_id): channel_id redis_client.get(fvisit:{visitor_id}) if channel_id: channel Channel.query.get(int(channel_id)) user.parent_id channel.user_id # 绑定上级 user.source_channel_id channel.id # 记录来源渠道 db.session.commit() def attribute_order(order, user): # 优先用用户注册时绑定的渠道没有则查最近点击 if user.source_channel_id: order.channel_id user.source_channel_id else: recent redis_client.get(fvisit:{user.visitor_id}) if recent: order.channel_id int(recent) db.session.commit()这里有个容易翻车的点user.source_channel_id一旦写入就不再变但如果业务允许用户换绑上级归因就会和绑定关系脱节。常见做法是订单归因只认「下单时刻的有效绑定」而不是注册时刻的快照。改的时候要同步改attribute_order和结算逻辑否则会出现「佣金算给了旧上级」的玄学问题。3.3 佣金计算与异步结算佣金结算放在 Celery 任务里订单状态变成「已完成」后触发。结算逻辑要处理三种情况一级佣金、二级佣金、以及退款冲正。# app/tasks/settle_task.py 佣金结算任务 celery.task def settle_order(order_id): order Order.query.get(order_id) if order.status ! completed: return user User.query.get(order.user_id) # 一级佣金直接上级 if user.parent_id: rate get_rate(user.parent_id, level1) # 从配置或等级表取比例 amount round(order.amount * rate, 2) Commission.create(order.id, user.parent_id, amount, level1) # 二级佣金上级的上级比例更低 parent User.query.get(user.parent_id) if parent and parent.parent_id: rate2 get_rate(parent.parent_id, level2) amount2 round(order.amount * rate2, 2) Commission.create(order.id, parent.parent_id, amount2, level2) db.session.commit()get_rate是分佣比例的统一入口按用户等级或全局配置返回。二次开发时想改成三级分销就在这个函数里加level3分支同时确认commission表能存下多条记录——它本来就是一对多设计不用改表结构。退款冲正的做法是监听退款事件把对应commission记录的status置为reversed而不是物理删除留痕方便对账。4. 本地跑起来从建库到看到第一笔佣金4.1 环境准备与配置修改先把代码拉下来建虚拟环境装依赖。数据库用 MySQL 5.7 以上Redis 本地起一个就行。python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt然后改config.py把数据库和 Redis 地址换成你自己的# config.py 需要改的几项 SQLALCHEMY_DATABASE_URI mysqlpymysql://root:password127.0.0.1:3306/promotion REDIS_URL redis://127.0.0.1:6379/0 CELERY_BROKER_URL redis://127.0.0.1:6379/1 ATTRIBUTION_WINDOW 7 * 86400 # 归因窗口秒CELERY_BROKER_URL用了 Redis 的 1 号库和业务缓存分开避免 key 冲突。这个习惯建议保留不差那点内存。4.2 建表与初始化数据项目里一般会带一个manage.py用 Flask CLI 注册了建表和初始化命令export FLASK_APPmanage.py flask db init # 如果用的是 Flask-Migrate flask db migrate -m init tables flask db upgrade flask init-data # 自定义命令插入测试用户和渠道如果源码里没有flask init-data常见做法是手动往user和channel表各插一条测试数据link_code随便填个test001然后用http://127.0.0.1:5000/track/test001访问看 Redis 里有没有生成visit:开头的 key。这一步能验证归因链路的前半段是否通。4.3 启动服务与验证佣金开三个终端一个跑 Flask一个跑 Celery worker一个跑 Redis如果没做成服务。flask run --port 5000 celery -A app.tasks.settle_task worker -l info验证流程访问推广链接 → 注册用户 → 手动改数据库把订单状态置为completed→ 触发settle_order任务 → 查commission表看有没有生成记录。如果 Celery 没反应先看 worker 日志里有没有收到任务再看 broker 地址对不对。这一步跑通整套系统的核心闭环就算验证完了。5. 避坑与排查推广系统上线前容易踩的五个坑5.1 点击量暴涨但归因率为零现象是 Redis 里channel:click:计数一直在涨但order.channel_id全是空。原因通常是 Cookie 里的visitor_id没带到注册请求里或者注册接口没调bind_channel_on_register。解决方法是抓一次注册请求的 Cookie确认visitor_id存在再检查注册视图里有没有调用绑定服务。我一般会在注册成功后打一条日志把visitor_id和绑定结果一起输出方便对。5.2 佣金重复结算同一笔订单生成了两条一级佣金记录。原因是settle_order任务被重复触发比如订单状态更新了两次或者 Celery 任务重试。解决办法是在Commission.create前加一个幂等判断按order_id user_id level查重已存在就跳过。更稳妥的做法是给commission表加唯一索引让数据库兜底。5.3 提现审核后余额没扣现象是审核通过了但用户可提现余额没变。原因是提现逻辑只改了withdraw表状态没同步扣减user.balance。解决方法是把「审核通过」和「扣余额」放在同一个数据库事务里任何一步失败都回滚。别用两个独立接口分别调网络抖动就会导致状态不一致。5.4 归因窗口过期后订单仍被算入用户点击后第 10 天下单按理不该归因但order.channel_id还是有值。原因是attribute_order只查了user.source_channel_id没校验时间。解决方法是给user表加一个bind_time字段归因时判断now - bind_time ATTRIBUTION_WINDOW超窗口就不写channel_id。5.5 Celery 任务堆积导致佣金延迟订单完成后几小时佣金还没到账。先看 Celery 队列长度如果堆积严重常见原因是 worker 并发数太低或任务里有慢查询。把worker --concurrency调大或者把结算任务里的逐条查询改成批量查询。另外确认settle_order没有在任务里做 HTTP 请求异步任务里发网络请求是延迟的常见来源。6. 二次开发改分佣规则和加渠道类型的实操思路改这套系统最高频的需求就两个调分佣比例、加新的渠道类型。分佣比例我建议不要散落在代码里而是抽一张commission_rule表字段包括level、min_amount、max_amount、rateget_rate函数改成查这张表。这样运营改比例不用发版加一条记录就行。下面是一个简化的规则查询实现# app/services/settlement.py 按订单金额区间取费率 def get_rate(user_id, level, order_amount): rule CommissionRule.query.filter( CommissionRule.level level, CommissionRule.min_amount order_amount, CommissionRule.max_amount order_amount ).order_by(CommissionRule.min_amount.desc()).first() return rule.rate if rule else 0.0加渠道类型时别急着改channel表结构。常见做法是加一个channel_type字段取值如link、qrcode、coupon然后在track视图里按类型走不同分支。二维码渠道多一步生成图片的逻辑优惠券渠道多一步校验券码有效性。这样扩展不用动归因和结算的核心代码风险可控。验证改造是否成功我习惯用一个小脚本模拟完整链路生成链接 → 模拟点击 → 注册 → 下单 → 触发结算 → 查佣金。脚本跑通再上测试环境比手工点页面靠谱得多。从那以后我每次改分佣逻辑都强制走一遍这个脚本确认一级、二级、退款冲正三种情况都覆盖到才敢提交。希望这套拆解能帮到你源码跑起来后先从attribution.py和settlement.py这两个文件下手改起来心里有底。本文还有配套的精品资源点击获取