[插件] 广告位管理(adslot)插件发布

👑Lv.11 元老 🌏 正式会员
2026-09-30 13:04:55

版本:1.0.0 | 作者:FlintHub 功能:右侧栏 / 移动侧栏的多区块广告位——三种广告类型(代码 / 图片 / 链接)+ 拖拽排序 + 有效期与过期提醒 + 点击/曝光统计;代码型 banner 存原文、渲染时经自建白名单净化。


一、功能简介

1.1 后台(三 Tab)

  • 广告项:增删改 + 筛选(按区块 / 按状态 / 标题搜索)+ 拖拽排序 + 单个启停(AJAX 就地更新)+ 批量启停(表单 PRG)+ 有效期日期
  • 区块:增删改 + 位置 / 样式 / 展示条数 / 排序 / 启停;区块下还有广告时拒绝删除(防孤儿广告项)
  • 设置:总开关、默认展示条数、点击统计开关、曝光统计开关、过期提醒天数

1.2 广告类型

类型 值 内容字段 说明
代码 code content_code(textarea,最多 200000 字符) 贴 HTML 片段;存原文,渲染时净化
图片 image content_image_url(https 外链)/ image_file(上传)/ content_image_current(编辑时保留原图) 上传走核心 Upload::file(),入库为裸相对路径
链接 link content_link(最多 200 字符) 纯文字条目

1.3 前台展示

  • 位置:sidebar(PC 右栏,用户面板之后)、sidebar_bottom(友链区块上方,PC 右栏 + 移动侧栏各渲染一份)
  • 样式:card(卡片式)/ compact(紧凑式)/ media(图文式,额外渲染 description)
  • 渲染规则:区块无可用广告项 → 整个区块不输出(不留空容器);总开关关闭 → 面板与样式都不注入
  • 可点击条目:<a href="{BASE_PATH}/ad-slot/click/{id}">,外链不直出到前台 HTML

1.4 点击中转

GET /ad-slot/click/{id} 四道校验(全部通过才 302):① 广告存在 ② 启用 ③ 未过期 ④ 所属区块启用。 任一不过 → 404(不 302 到首页,避免「点了没反应」的迷惑行为)。 通过 → 计一次点击 → Location: {外链} + Cache-Control: no-store + X-Robots-Tag: noindex。

1.5 统计口径

  • 点击:中转页同步 UPDATE ads_items SET clicks = clicks + 1
  • 曝光:渲染期只把 id 收进 $GLOBALS['adslot_seen'](按 id 去重,同请求内两处面板只计 1 次), 落库收敛到 route_after_dispatch 钩子 → 一条批量 UPDATE;未渲染广告的请求 empty() 即返回,零查询
  • 后台列表直接显示 clicks / impressions 数字(明细留后续)

二、渲染与请求流程

请求进入
  └─ layout_head_end        → 总开关开 → 注入 assets/style.css(带 filemtime 版本号)
  └─ right_sidebar_after    → Plugin::renderPosition('sidebar')
  └─ sidebar_above_friend_links → Plugin::renderPosition('sidebar_bottom')   ← 双触发(PC + 移动)
        └─ blocksByPosition() → loadActive()
              · 首次:2 条查询(区块 + 广告项,请求内静态缓存)
              · 之后:0 条查询(同一请求内两个钩子共享缓存)
        └─ views/panel.php   → 逐条渲染;code 型调 sanitizeAdHtml(),image 型调 imageSrc()
                             → 每条调 markSeen()(曝光默认关 → 直接短路)
  └─ route_after_dispatch   → 有已展示 id → 1 条批量 UPDATE 落曝光;否则零查询

每请求查询预算:实测首次渲染 2 条、第二次 0 条、总开关关闭 0 条(规范 §十三 要求 ≤5)。


三、路由与接口

3.1 前台(hook/route_register.php)

方法 路径 说明 登录 CSRF
GET /ad-slot/click/{id} 点击中转:校验 → 计数 → 302 / 404 不需要 不需要

3.2 后台(hook/admin_route_register.php,路径不写 /admin 前缀,核心自动补)

方法 路径 说明 响应
GET /admin/adslot 列表页(三 Tab;?tab= ?block_id= ?status= ?q=) 视图
POST /admin/adslot/item/save 新增 / 编辑广告项(三种类型各自字段名) 302 PRG
POST /admin/adslot/item/delete 删除广告项 302 PRG
POST /admin/adslot/item/toggle 单个启停 JSON
POST /admin/adslot/item/batch 批量启停(ids[] + active) 302 PRG
POST /admin/adslot/item/sort 拖拽排序(order[]) JSON
POST /admin/adslot/block/save 新增 / 编辑区块 302 PRG
POST /admin/adslot/block/delete 删除区块(有广告时拒绝) 302 PRG
POST /admin/adslot/block/sort 区块拖拽排序(order[]) JSON
POST /admin/adslot/settings/save 保存全局设置 302 PRG
  • 传统表单:Csrf::verifyOrDie();AJAX(toggle / sort):Csrf::verify() 自行回 JSON (verifyOrDie() 会 echo HTML 并 exit,AJAX 收到的是 HTML 不是 JSON)
  • 门禁来自继承 app\Controllers\Admin\BaseController → 构造函数完成「管理员 + 二次验证」双重校验

四、后台可配项

设置键 默认 范围 说明
adslot_enabled 1(开) 0 / 1 总开关;关闭后前台无面板、无样式注入,0 条查询
adslot_default_limit 5 1 ~ 20 区块未填条数时的默认展示条数
adslot_stat_click 1(开) 0 / 1 关闭后中转仍 302,但 clicks 不增
adslot_stat_impression 0(关) 0 / 1 曝光默认关闭(避免每请求写库)
adslot_expire_days 7 0 ~ 365 剩余天数 ≤ 该值 → 后台列表标「即将过期」;0 = 不提醒

区块级字段:position(sidebar / sidebar_bottom)、style(card / compact / media)、 limit_num(1 ~ 20)、sort_order、is_active。 广告项级字段:sort_order、is_active、expire_at(日期 → 当天 23:59:59 的时间戳,空 = 永久)。

⚠️ 保存设置的「字段缺失」口径:整数项用 array_key_exists() 缺失即跳过(不写最小值); 三个 checkbox 用隐藏哨兵 adslot_form_submitted 判定「表单提交过」,未勾选 → 写 0。


五、数据表

独立库:plugins/adslot/data/adslot.sqlite(WAL;同目录 .htaccess + web.config 拦直接访问)

表 用途
ads_blocks 区块:id / name / title / position / style / limit_num / sort_order / is_active / created_at / updated_at
ads_items 广告项:id / block_id / type / title / content / url / description / sort_order / is_active / expire_at / clicks / impressions / created_at / updated_at
索引 列
idx_ads_items_block ads_items(block_id, is_active, sort_order)
idx_ads_blocks_pos ads_blocks(position, is_active, sort_order)
  • 建表走内容门控:SCHEMA_VERSION = 'adslot-schema-1',戳文件 data/schema.version
  • 表/索引 DDL 均 IF NOT EXISTS,索引 DDL 排在 CREATE TABLE 之后
  • uninstall():先 DROP 两张表 → 再清 settings 里 5 个 adslot_* → 最后删戳文件

六、目录结构

plugins/adslot/
├── plugin.json                     # 7 个钩子 / permissions: route:admin, system:settings
├── Plugin.php                      # 主类:建表 / 取数 / 渲染 / 中转 / 净化 / 后台业务方法
├── AdminController.php             # 后台控制器(继承 Admin\BaseController)
├── FrontController.php             # 前台点击中转
├── hook/
│   ├── init_after.php              # 惰性建表兜底(命中戳文件即短路,零查询)
│   ├── route_register.php          # /ad-slot/click/{id}
│   ├── admin_route_register.php    # 10 条后台路由
│   ├── right_sidebar_after.php     # 右栏主广告位
│   ├── sidebar_above_friend_links.php  # 右栏底部广告位(双触发)
│   ├── route_after_dispatch.php    # 曝光批量落库(1 条 UPDATE)
│   └── layout_head_end.php         # 注入 style.css
├── views/
│   ├── panel.php                   # 前台面板($this 不可用,转义/i18n 收敛在 Plugin)
│   └── admin.php                   # 后台三 Tab + 弹层 + 表单
├── assets/  admin.css · admin.js · style.css
├── lang/    zh.php · en.php · zh_tw.php   (各 100 键,键集合与键序严格一致)
└── data/    adslot.sqlite · schema.version · .htaccess · web.config
Last edited by flinthub at 2026-09-30 13:40
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| Views 13 | Replies 4

All Replies (1)

💡Lv.10 顾问 🌏 正式会员
2026-09-30 13:15:48
插件后台设置点进去就出现了500错误
知识,奉行,知行合一
#1 floor

Please Log in