简介:这份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 关键依赖 Flask==2.3.2 # Web 框架,提供路由和请求上下文 SQLAlchemy==2.0.19 # ORM,映射用户、渠道、订单、佣金四张核心表 Flask-SQLAlchemy==3.0.5 redis==4.6.0 # 点击去重、渠道链接缓存 celery==5.3.1 # 异步结算佣金、提现审核 pymysql==1.1.0 # MySQL 驱动这里有个细节值得注意:Flask 版本锁在 2.3.x,SQLAlchemy 用的是 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 数据表关系速览
四张核心表的关系用一句话概括:一个用户可以有多个渠道,一个渠道产生多笔订单,一笔订单对应一条或多条佣金流水。关键字段如下:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| user | id, parent_id, invite_code | parent_id 记录上级,形成分销树 |
| channel | id, user_id, link_code, click_count | link_code 是推广链接唯一标识 |
| order | id, user_id, channel_id, amount, status | channel_id 记录归因结果 |
| commission | id, order_id, user_id, amount, status | status 控制待结算/已结算/已提现 |
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_code=link_code).first() if not channel: return redirect('/') # 无效链接直接回首页,不报错暴露细节 # 点击计数用 Redis 自增,避免每次写库 redis_client.incr(f'channel:click:{channel.id}') # 把渠道 ID 写入 Redis,key 用访客标识,有效期 7 天 visitor_id = request.cookies.get('visitor_id') or str(uuid.uuid4()) redis_client.setex(f'visit:{visitor_id}', 7 * 86400, channel.id) resp = redirect(channel.target_url or '/') resp.set_cookie('visitor_id', visitor_id, max_age=7 * 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(f'visit:{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(f'visit:{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, level=1) # 从配置或等级表取比例 amount = round(order.amount * rate, 2) Commission.create(order.id, user.parent_id, amount, level=1) # 二级佣金:上级的上级,比例更低 parent = User.query.get(user.parent_id) if parent and parent.parent_id: rate2 = get_rate(parent.parent_id, level=2) amount2 = round(order.amount * rate2, 2) Commission.create(order.id, parent.parent_id, amount2, level=2) db.session.commit()get_rate是分佣比例的统一入口,按用户等级或全局配置返回。二次开发时想改成三级分销,就在这个函数里加level=3分支,同时确认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 = 'mysql+pymysql://root:password@127.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_APP=manage.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、rate,get_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这两个文件下手,改起来心里有底。
本文还有配套的精品资源,点击获取