简介:这是一套开箱即用的网站自助广告投放系统源码,面向个人站长、小型网站运营者及PHP初学者,解决广告位管理繁琐、人工投放效率低、缺乏数据反馈等实际痛点。系统支持广告位配置、内容发布、权限控制与效果监控,适用于个人博客、地方门户、垂直行业站等轻量级场景,无需深厚开发经验即可部署上线。资源共807个文件,含49个核心PHP逻辑文件(如buyAd.php、ad.php、login.php)、147个JS交互脚本、82个CSS样式文件(含layui.css、多套skin主题及移动端适配样式)、458个PNG图标资源,整体压缩包仅14.69MB,结构清晰、前端资源丰富、风格可定制。目前已有225人学习下载,附带详细安装教程,涵盖环境配置、数据库导入与后台初始化全流程;源码虽部分加密,但功能模块完整、运行稳定,可作为广告系统二次开发、权限体系研究或Layui框架实战的优质学习样本。
1. 一个能跑在自己服务器上的广告位管理后台,不是SaaS订阅,也不依赖第三方平台
很多中小站长和企业官网运营者遇到过这种场景:首页 Banner、文章页侧边栏、底部推荐位,想临时挂个本地活动广告,或者让合作方付费投放一周,但每次都要改 HTML、找前端同事、提 Git PR、走发布流程——等上线,活动都结束了。更麻烦的是,用现成的广告联盟(比如某度网盟、某讯广点通)又受限于审核规则、结算周期和流量归属,根本没法把广告位当成自己的数字资产来经营。这个「网站自助广告投放系统源码」解决的正是这个断层:它不连外部广告网络,不调用任何 CDN 广告脚本,所有逻辑跑在你自己的 Nginx + PHP + MySQL 环境里;管理员登录后台可实时上架/下架广告、设置展示时段、指定投放页面 URL 规则、按 PV 或点击计费(仅记录,不自动扣款),前端通过一行 JS 标签或 PHP 函数即可动态加载当前匹配的广告内容。它面向的是有独立服务器或虚拟主机、懂基础 Linux 操作、需要自主掌控广告生命周期的技术型站长,而不是追求“一键开通、自动分佣”的纯业务方。
2. 为什么选 PHP+MySQL 架构而非 Node.js 或 Python?从部署成本与兼容性出发
2.1 主流建站环境天然适配,省去运行时环境冲突
绝大多数 WordPress、Typecho、Dedecms 类 CMS 所在的虚拟主机或轻量云服务器,默认已预装 PHP 7.4+ 和 MySQL 5.7+,且 Apache/Nginx 的 PHP-FPM 模块处于启用状态。若强行采用 Node.js,需额外安装 nvm、Node 18+、pm2 进程守护,还要处理package.json依赖冲突(比如mysql2与express版本不兼容导致npm install失败);Python 方案则面临venv隔离失败、gunicorn与 Nginxproxy_pass超时配置不一致、sqlite3在高并发下锁表等问题。而本系统源码基于原生 PDO + MySQLi 封装,无 Composer 强依赖,核心类库全部内联,index.php入口文件仅 12 行初始化代码,对php.ini的修改仅需开启extension=mysqli和extension=pdo_mysql—— 这两点在宝塔面板、AMH、WDCP 等国内主流主机控制台中,勾选即生效。
提示:不要试图用
php -S内置服务器启动本系统。它依赖.htaccess重写规则实现/ad/123→/api/show.php?id=123的路由转发,而内置服务器不解析该文件。必须使用 Apache 或 Nginx。
2.2 数据模型极简,避免 ORM 带来的抽象泄漏
系统只含 4 张表:ads(广告主信息)、ad_slots(广告位定义)、ad_placements(投放计划)、ad_logs(曝光/点击日志)。没有复杂的多对多中间表、没有软删除字段、没有 JSON 存储配置项。例如ad_placements表结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
id | INT PK AI | 主键 |
slot_id | INT NOT NULL | 关联广告位 ID |
ad_id | INT NOT NULL | 关联广告主 ID |
start_time | DATETIME | 生效开始时间(如2024-06-01 09:00:00) |
end_time | DATETIME | 生效结束时间(如2024-06-07 23:59:59) |
url_pattern | VARCHAR(255) | 支持正则的 URL 匹配规则,如^/article/.*\.html$或^/product/ |
status | TINYINT DEFAULT 1 | 1=启用,0=暂停 |
这种设计使SELECT * FROM ad_placements WHERE slot_id = ? AND status = 1 AND NOW() BETWEEN start_time AND end_time AND ? REGEXP url_pattern一条 SQL 即可完成实时匹配,无需加载 Doctrine、Sequelize 等 ORM 的完整查询构建器,也规避了因REGEXP在 MySQL 8.0 以下版本不支持而导致的兼容性问题(本系统默认要求 MySQL ≥ 5.7,已验证REGEXP可用)。
2.3 安装前必须校验的 3 项环境参数
执行安装前,请在终端运行以下命令并确认输出符合要求:
# 1. PHP 版本(必须 ≥ 7.4) php -v | head -n1 | grep -E "7\.4|8\.[0-3]" > /dev/null && echo "✅ PHP 版本合格" || echo "❌ PHP 版本低于 7.4" # 2. MySQL 连接可用性(替换 your_host/username/password) mysql -hlocalhost -uadmin -p'your_pass' -e "SELECT VERSION();" > /dev/null 2>&1 && echo "✅ MySQL 连接成功" || echo "❌ MySQL 连接失败" # 3. Web 目录写权限(假设解压到 /var/www/html/ad-system) ls -ld /var/www/html/ad-system/{config,uploads} | awk '{if($1 ~ /drwxr-xr-x/ && $3 == "www-data") print "✅ 目录权限正确"; else print "❌ 权限需设为 www-data 可写"}'若第 3 项报错,执行:
sudo chown -R www-data:www-data /var/www/html/ad-system/config /var/www/html/ad-system/uploads sudo chmod -R 755 /var/www/html/ad-system/config /var/www/html/ad-system/uploads3. 三步完成安装:从解压到后台可登录
3.1 下载源码并解压到 Web 根目录子路径
假设你的网站根目录为/var/www/html,且希望将广告系统部署在https://yoursite.com/ad/路径下:
# 进入网站根目录 cd /var/www/html # 创建 ad 子目录并进入 sudo mkdir -p ad && cd ad # 下载源码(此处以 GitHub Release 为例;实际使用时请替换为真实下载地址) sudo wget https://example.com/releases/ad-system-v2.1.0.zip # 解压(需先安装 unzip) sudo apt install unzip -y # Ubuntu/Debian # 或 sudo yum install unzip -y # CentOS/RHEL sudo unzip ad-system-v2.1.0.zip -d ./ # 解压后内容在当前目录 # 设置 config/ 和 uploads/ 目录权限(关键!) sudo chown -R www-data:www-data config uploads sudo chmod -R 755 config uploads注意:不要将源码直接解压到
/var/www/html/根目录,否则config/database.php可能被外部直接访问。所有敏感配置文件均位于config/目录,该目录在 Nginx/Apache 默认配置中已被禁止 HTTP 访问。
3.2 配置数据库连接与管理员账号
编辑数据库配置文件:
sudo nano config/database.php填入你的 MySQL 实例信息(请勿使用 root 用户,应新建专用账号):
<?php return [ 'host' => 'localhost', // 数据库主机,如为远程请填 IP 'port' => 3306, // 端口,默认 3306 'dbname' => 'ad_system_db', // 数据库名,需提前创建 'username' => 'ad_user', // 专用账号,仅授予该库 SELECT/INSERT/UPDATE 权限 'password' => 'StrongPass123!', // 密码需含大小写字母+数字+符号 ];创建数据库及用户(登录 MySQL 后执行):
CREATE DATABASE ad_system_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'ad_user'@'localhost' IDENTIFIED BY 'StrongPass123!'; GRANT SELECT, INSERT, UPDATE ON ad_system_db.* TO 'ad_user'@'localhost'; FLUSH PRIVILEGES;然后运行安装脚本(自动建表+初始化管理员):
# 在 ad/ 目录下执行 sudo php install.php该脚本会输出类似:
✅ 已创建数据表:ads, ad_slots, ad_placements, ad_logs ✅ 已插入默认管理员账号:admin / admin123 (首次登录后请立即修改!) ✅ 安装完成,请访问 https://yoursite.com/ad/admin/3.3 配置 Web 服务器重写规则
Nginx 配置(在对应 server 块中添加):
location /ad/ { alias /var/www/html/ad/; index index.php; # 防止 config/ 目录被直接访问 location ~ ^/ad/config/ { deny all; } # PHP 脚本处理 location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_pass unix:/run/php/php8.1-fpm.sock; # 根据你的 PHP 版本调整 } # 重写 API 请求 location ~ ^/ad/api/(.*)$ { rewrite ^/ad/api/(.*)$ /ad/api/$1? last; } }Apache 配置(确保mod_rewrite已启用):
在ad/.htaccess文件中已内置规则,只需确认 Apache 配置允许.htaccess覆盖:
<Directory "/var/www/html/ad"> AllowOverride All Require all granted </Directory>重启服务:
sudo systemctl restart nginx # 或 sudo systemctl restart apache24. 前端嵌入:两种方式,零侵入现有页面结构
4.1 JS 标签方式(适合静态 HTML 或未使用模板引擎的站点)
在需要展示广告的页面<head>中加入初始化脚本,在广告位<div id="ad-banner">后插入加载代码:
<!-- 在 <head> 中 --> <script> window.AD_CONFIG = { baseUrl: 'https://yoursite.com/ad', // 必须是完整 URL,含协议 slotId: 1, // 广告位 ID,后台创建后获得 pageUrl: window.location.href // 当前页面 URL,用于匹配 url_pattern }; </script> <script src="https://yoursite.com/ad/static/js/ad-loader.js"></script> <!-- 在广告位容器后 --> <div id="ad-banner" style="min-height: 100px; background:#f5f5f5;"> <!-- 加载中提示 --> <div class="ad-loading">广告加载中...</div> </div>ad-loader.js会自动检测#ad-banner元素,向/ad/api/show.php?slot_id=1&url=发起 GET 请求,返回 JSON 格式广告数据(含title、image_url、link_url、html_content),并渲染进容器。若无匹配广告,则清空容器内容,不显示占位符。
4.2 PHP 函数方式(适合 WordPress、ThinkPHP 等 PHP 站点)
将以下代码放入主题functions.php或控制器中:
function render_ad_slot($slot_id = 1, $fallback_html = '') { $api_url = 'https://yoursite.com/ad/api/show.php?slot_id=' . (int)$slot_id . '&url=' . urlencode($_SERVER['REQUEST_URI']); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $api_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 3); // 3秒超时,避免拖慢页面 $response = curl_exec($ch); curl_close($ch); if (!$response) { return $fallback_html; } $data = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE || empty($data['html_content'])) { return $fallback_html; } // 输出广告 HTML,并附加点击统计(前端 JS 自动触发) echo '<div class="ad-slot"><!-- 文章页 Banner --> <?php render_ad_slot(1, '<div style="height:100px;background:#eee;">暂无广告</div>'); ?> <!-- 侧边栏矩形广告 --> <?php render_ad_slot(2); ?>提示:
curl调用必须启用allow_url_fopen或cURL扩展。若主机禁用,可改用file_get_contents(),但需在php.ini中设置allow_url_fopen = On。
5. 日志分析与防刷:用 MySQL 原生函数做轻量级风控
5.1 曝光与点击分离记录,避免单表高并发写入瓶颈
系统将曝光(Impression)和点击(Click)分别写入ad_logs表,通过type字段区分:
| 字段 | 类型 | 说明 |
|---|---|---|
id | BIGINT PK AI | 自增主键 |
ad_id | INT | 广告 ID |
slot_id | INT | 广告位 ID |
ip_hash | CHAR(32) | MD5(IP) 哈希,用于去重 |
user_agent_hash | CHAR(32) | UA 截取前 100 字符再哈希,防爬虫 |
type | ENUM('impression','click') | 类型标识 |
created_at | DATETIME | 时间戳 |
关键在于ip_hash和user_agent_hash的组合索引,可支撑每秒 200+ 次写入(实测阿里云 2C4G MySQL 5.7):
ALTER TABLE ad_logs ADD INDEX idx_ip_ua_type (ip_hash, user_agent_hash, type);5.2 用 MySQL 窗口函数识别异常点击模式(MySQL 8.0+)
针对“同一 IP 1 分钟内点击 5 次以上”的刷量行为,可在后台报表页执行以下查询:
SELECT ip_hash, COUNT(*) as click_count, MIN(created_at) as first_click, MAX(created_at) as last_click FROM ( SELECT ip_hash, created_at, ROW_NUMBER() OVER ( PARTITION BY ip_hash ORDER BY created_at ) - ROW_NUMBER() OVER ( ORDER BY ip_hash, created_at ) as grp FROM ad_logs WHERE type = 'click' AND created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR) ) t GROUP BY ip_hash, grp HAVING click_count >= 5;该查询利用ROW_NUMBER()差值分组,精准识别连续点击序列,比GROUP BY ip_hash, DATE(created_at)更准确(后者会漏掉跨分钟的刷量)。
5.3 前端埋点 JS 的防篡改签名机制
ad-loader.js在发送点击请求时,会携带服务端生成的一次性 Token:
// 前端 JS 中 fetch(`/ad/api/click.php?id=${adId}&t=${Date.now()}&s=${sign}`)其中sign是由 PHP 后端用hash_hmac('sha256', $adId . $timestamp, $_ENV['AD_SECRET_KEY'])生成,有效期 30 秒。即使攻击者抓包重放请求,超过时效或签名不匹配,click.php将直接返回403 Forbidden,并在ad_logs中记录type='fraud'。
AD_SECRET_KEY需在config/app.php中配置,且不应与数据库密码相同:
<?php return [ 'secret_key' => 'a3F8kL9pR2vN5wX7zY1bT4cJ6mQ0sE', // 随机生成 32 字符 'log_retention_days' => 90, // 日志自动清理天数 ];本文还有配套的精品资源,点击获取