v1.4.2这一版干了两件事:把明亮模式的品牌色全部撤掉(文字回归正文色,层级改由字号和字重承担),以及把左侧那枚小圆点强化成三层叠加的呼吸灯——它是整条里唯一的品牌色落点。为什么这么改,第九节讲。
一、它长什么样
首页上就一行:
四个细节让它不像一块死板的信息牌:
- 一条一条滚。旧条往上滑出、新条从下方弹入,滑到六成时过冲一点再回落——就是那下「跳」的手感。两行绝对定位叠在同一个窗口里,所以看着是连续的滚动带,不是内容在刷新。
- 四类事一起播。下载、注册、登录、访问混在同一条时间轴上按先后滚,不用切换页签,也不分区块。
- 左右两端是活的。左边那枚圆点是呼吸灯(缩放 + 透明度 + 向外扩散的柔光晕,2 秒一轮);右边「今日 1,286 次」在数据刷新且数字变化时会弹一下(520ms)。
- 点得动。播报里只有下载这类带资源页,整行是链接,点进去就是那个资源——播报本身就是个入口。注册 / 登录 / 访问没有落地页,保持纯文本,不做无意义的可点区域。
悬停会暂停轮播(鼠标移开继续);页面切到后台时完全不动画,省电,也避免切回来时一次性连跳好几条。
窄屏(≤767px)自动收窄:前缀文字和「今日」二字藏起来只留呼吸点与数字,行高降到 34px,字号 13px,把宽度全让给资源名。
二、四类动态,数据从哪来
这是装之前最该搞清楚的一张表。四类事件的数据来源完全不同,其中「访问」是最特殊的一个:
| 事件 | 文案 | 数据来源 | 说明 |
|---|---|---|---|
download |
下载了《…》 | 钩子 guaqi_count_updated |
官方下载入口触发;只认 type === 'resource',浏览量等其它计数走同一个 action,必须过滤掉 |
register |
加入了本站 | 钩子 user_register |
第三方聚合登录首次建号时同样会触发,不需要额外对接 |
login |
登录了 | 钩子 wp_login |
账号密码登录与第三方授权登录都走到这里;同一用户 30 分钟内只记一次 |
visit |
访问了本站 | 只读官方签到表 {prefix}guaqi_checkins |
不自己记录,直接读官方数据;官方已按「每人每天一条」去重,天然不刷屏 |
四类事写进同一张自建表 {prefix}guaqi_download_feed(访问类除外,它在官方表里),查询时两个来源各自带着时间窗查出来,在 PHP 侧合并排序,不做 SQL UNION。这样两边都能走自己的索引,时间语义也不用在 SQL 里绕。
三、为什么「访问」要去读官方签到表
这一节是给想自己造轮子的人看的,它解释了一个很容易踩空的坑。
正常的 WordPress 站点要记「有人访问了一页」,挂 wp 或 template_redirect 就行了。但瓜奇这套是 headless:前端是 Nuxt,WordPress 只提供 GraphQL 接口,并不 serve 页面。也就是说真实访客打开网页时,WordPress 那几个钩子根本不会触发——挂上去会得到永远为空的记录,而且很难看出是错的。
那前端访问时到底留下了什么?前端每次页面访问会调一个 gqPageVisit mutation,登录用户会落进 {prefix}guaqi_checkins,官方已经按「每人每天一条」帮你去重了。那就是「今天谁来过」的权威数据——再自己记一份只是重复劳动。
代价也要说清楚:官方那张表只存了 frontend_url,没存 path。所以拿不到「访问了哪一页」,只能得到「今天谁来过」。这个限制在代码里、在调试接口里都明写着,不是漏做。
{prefix}guaqi_feed(Common/Feed 模块),但它其实是内容动态流——kind 只收 publication / comment,且强制要求 postId >= 1。注册、登录、访问这三类没有 postId 的事件根本塞不进去。所以自建表是正解,别再试着去接官方 Feed 了。四、装它:三步,加一次验收
- 装包。瓜奇 builder → 插件 → 安装
guaqi-download-feed-1.4.2.zip。 - 拖模块。把「最新动态」拖到想要的位置——首页顶部、资源列表上方、侧栏都行。它只有一行,放哪都不挤,也不绑定首页,任意页面都能拖。
- 点发布。
验收方法:用一个非管理员账号去下载一个资源,几秒之内首页那条播报应该就出现了。别用管理员账号测——原因见第七节的「噪音过滤」,而且站长的登录/访问本来就被特意过滤掉了。
五、设置项速查(11 项)
模块设置里能改的都在 builder 右侧面板:
| 设置 | 范围 | 默认 | 说明 |
|---|---|---|---|
| 播报条前缀文字 | 文本(40 字内) | 最新动态 | 左边那句,留空则只留呼吸点 |
| 轮播池条数 | 3–20 | 8 | 池子越大,同一条再出现的间隔越长 |
| 播报间隔(秒) | 2–15 | 4 | 太快看不清,太慢没有动态感,4 秒是试出来最舒服的 |
| 显示今日动态条数 | 开 / 关 | 开 | 关掉右边会清爽,但也少了一半「活」的观感 |
| 播报「谁下载了」 | 开 / 关 | 开 | 四类事件独立开关。全关掉会自动退回只看下载,避免请求落空 |
| 播报「谁注册了」 | 开 / 关 | 开 | |
| 播报「谁登录了」 | 开 / 关 | 开 | |
| 播报「谁访问了」 | 开 / 关 | 开 | |
| 用户名脱敏 | 开 / 关 | 开 | zenghe → z***e。建议保持开 |
| 显示资源标题 | 开 / 关 | 开 | 关掉只剩「某人下载了」,适合极度保守的场景 |
| 数据刷新间隔(秒) | 0–600(0 = 关) | 60 | 刷新时游标按条目 id 跟随当前那条,不会跳回第一条 |
想改呼吸灯的颜色(默认品牌橙 #FF4612),在主题 CSS 里覆盖两个变量——光晕色得一起改,因为十六进制换不出 rgba:
:root {
--gq-dlfeed-accent: #FF4612;
--gq-dlfeed-accent-glow: rgba(255, 70, 18, 0.45);
}
另外还有 --gq-dlfeed-edge(左侧竖条)、--gq-dlfeed-avatar-fg(头像字色)、--gq-dlfeed-skel(骨架条)三个中性色变量,以及 --gq-dlfeed-row-h(行高,默认 38px)——改高度只改这一个变量就够,切换动画用的是绝对定位 + 百分比位移,不吃行高。
六、一个必须先说的坏消息:历史补不回来
如果你以为「我站早有下载记录了,装上直接就能看到历史」,这盆冷水得先泼:官方没有「谁下载了什么」这个数据,历史补不回来。
我翻过官方源码,下载成功后它只做两件事(入口在 ResourceDownload::mutateAndGetPayload()):
| 官方写下的数据 | 表 / 字段 | 能算出什么 |
|---|---|---|
| 当日配额扣减 | {prefix}guaqi_daily_downloads |
某人某天下了几次(只有次数,没有对象) |
| 资源计数自增 | post_meta.guaqi_count.resource |
某资源被下过几次(只有总数,没有对象) |
两边都没有「谁在什么时候下了哪一篇」。合起来能算总量,拆不出明细。所以这插件的动态从装上的那一刻开始积累。
顺带纠正一个常见误解:下载走的是每日次数配额,不扣积分。所以积分流水里同样捞不到下载记录——别去那边找。
七、我替你定的六个取舍
| 决定 | 为什么 |
|---|---|
| 30 秒缓存,不做「写入即失效」 | 首页 QPS 最高,不能让每个访客都打一次库。而缓存键里含条数与开关,没法枚举删除;若改用自增版本号做键,每次事件都会新增一个 transient,高流量下会把 options 表撑大。30 秒自然过期 + 前端自己刷新,体感已经够低。 只有「今日计数」那一档缓存会在写入时主动清掉,因为它最需要即时。 |
| 同一个人只留最新一条 | 否则有人连下 5 个源码,整屏都是他一个人。 |
| 7 天时效窗 | 首页挂着「30 天前」的播报,比不显示更显冷清。超窗的记录只在库里留着,不参与展示。 |
| 登录 / 访问排除管理员,下载 / 注册保留 | 登录和访问是高频、低信息量的事件,站长天天登后台会把它整条淹没;下载和注册是低频、高信息量的,站长偶尔出现一次反而更真实。 细节:去重打标只在通过过滤之后做——站长的登录被跳过后,他更早的那条下载仍然有机会出现在播报里。 |
| 用户名默认脱敏 | zenghe → z***e,两字名 阿伟 → 阿***。下载行为被公开本身就是隐私暴露,脱敏是最低成本的体面。设置里可以关。 |
| 后台 90 天自动清理 | 每天跑一次 cron,单次最多删 5000 行,表不会无限涨。 |
八、三次线上事故的根因(这节最值钱)
下面三条都是真在线上炸过的,也都已经修在包里。如果你在用别人的插件,或者自己写类似的东西,这三条大概能帮你省一个晚上。
8.1 升级之后,下载动态整块消失,只剩「谁登录了」
现象:1.2.0 上线后,播报条只轮播登录事件,下载类一条都不剩。而 1.1.0 是好的。
排查一遍:钩子和签名都对、客户端没做二次过滤、三端事件白名单一致。唯一变的是查询条件——1.1.0 是 WHERE created_at >= X AND post_id > 0,根本不看 event_type;1.2.0 扩成四类事件后变成 WHERE created_at >= X AND event_type IN (...)。
根因:event_type 这一列是 1.2.0 新加的,靠 dbDelta 的 ADD COLUMN 给存量行补默认值。但 dbDelta 解析 CREATE TABLE 时会 trim 掉行尾的引号,生成的 ALTER 可能不带 DEFAULT——存量行于是停在空字符串,被新查询整批挡掉。老数据全在那儿,只是查不出来了。
修法:表结构版本 2 → 3,升级时无条件跑一次归类(幂等,全是合法值时更新 0 行):
UPDATE {prefix}guaqi_download_feed SET event_type = 'download'
WHERE event_type IS NULL
OR event_type NOT IN ('download','register','login','visit')
另外,升级逻辑挂在「表结构版本」的门闩上——只递增版本号才能让已装旧版的站点重跑一遍,光改代码不涨版本号是没用的。
8.2 点播报跳去了后端域名
现象:首页播报里点「谁下载了《瓜奇文字格子广告插件》」,跳进了 www.q0di.top/瓜奇文字格子广告插件.html——后端 WordPress 的文章页,而不是前站的资源页。
根因:取链接用了 get_permalink($post)。它是按后端 WordPress 域生成的(后端域/中文标题.html 那种形态),而前站是另一个域、路由是 /article/{id}——两条完全不同的路,域名和结构都对不上。
修法:改用官方 guaqi_get_single_slug($post)——官方给前站卡片写 url 用的就是这个函数,会带语言前缀、遵循站点的 slug 结构,那才是「文章 → 前站地址」的权威映射。取到的路径统一裁成站内相对路径,拿不到时兜底 /article/{id},再拿不到就返回空串(干脆不生成链接,保持纯文本)。
guaqi_get_single_slug(),绝不要用 get_permalink()。还有个附带好处:url 是渲染时实时查的、不入库,所以历史记录会被一起修正,不需要清表。
8.3 空数据永远卡在骨架条上
现象:站点冷启动时播报条一直转圈,看起来像坏了。
根因:原代码只判断了「请求是否失败」,没区分「还在加载」和「加载完了但确实没数据」。少这一个分支,空态就永远等不到。
修法:把三种状态拆开——首屏加载中给骨架条;拿到响应但没有条目(或请求失败)给空态文案「暂时没有新的动态」;有数据才渲染行。现在这三种情况在代码里是三个明确的返回分支,测试也专门盯着这条。
九、为什么整条只有一个橙点
v1.4.2 把明亮模式里的品牌色全撤了,只留左侧一枚呼吸灯。这不是审美偏好,是算过对比度的:
- 品牌橙
#FF4612落在白底上,对比度只有 3.4,达不到 13–15px 正文要求的 4.5 门槛。拿它写字,看着「品牌感很足」,实际读起来发飘——这个问题在手机小字号上尤其明显。 - 所以文字全部改成
color: inherit,直接跟随站点正文色。层级改由字号 + 字重 + 透明度承担:前缀 13px/600、名字 500、相对时间 12px/0.5 透明度、今日数字 15px/600。 - 整条唯一的品牌色落点就是那枚 7px 呼吸灯。它的职责是「正在发生」的信号,靠三层叠加把注意力拽过去:本体
scale(1 → 0.8)+ 透明度1 → 0.5+ 一圈从0 0 0 0扩散到0 0 0 6px的柔光晕。 - 竖条、头像、骨架条全部走中性灰——保留结构感,但不跟呼吸灯抢色。
文字用 inherit 还有个额外红利:站点如果靠 class 或属性切深色主题,它也会自动跟着变,不再只认系统的 prefers-color-scheme。
另外,系统开启「减弱动态效果」时:呼吸灯关掉动画但保留一圈静态柔光(「正在发生」的信号不该彻底消失),数字不跳,行的切换从位移改成 300ms 淡入淡出。
十、装上没显示?先打这个接口
这插件对外只有两个 action:plugin.guaqi.downloadFeed.list 负责取播报数据,plugin.guaqi.downloadFeed.debug 是排障入口。后者带一个密钥保护,一次性告诉你:表建没建、各类事件各有多少行、钩子挂上没有、当前身份是谁、签到表读得到多少、最近 10 条原始流水。密钥只存在于服务端,不会下发到浏览器。
mutation {
gqDownloadFeedDebug(input: { key: "gqfeed-diag-9f3c1a7b24e8" }) {
result
}
}
返回值里看几个关键字段就能定位卡在哪:
| 字段 | 含义 | 异常时怎么办 |
|---|---|---|
table.exists |
流水表建好了没 | false → 插件没真正启用,或数据库用户没建表权限 |
table.dbVersion |
表结构版本 | 不是 3 → 升级没跑,回看 8.1 |
table.byEvent |
各类事件各多少行 | 出现空键且行数很多 → 还有别的写入路径没打上 event_type |
hooks.guaqi_count_updated |
下载钩子挂上了没 | false → 插件没加载,或下载走的不是官方入口 |
identity.user_id |
后端认出的用户 | 为 0 → 这次请求没带上登录态,流水压根不会记 |
visitSource.recentRows |
签到表 7 天内的行数 | 0 → 前端没有上报访问,或 frontend_url 对不上 |
latest |
最近 10 条原始流水 | 有记录但前台不显示 → 记录超了 7 天时效窗,或被去重/噪音过滤挡了 |
十一、想自己改?四个扩展点
1)排除某些用户,不让他们出现在播报里(最常见的是排除管理员、或排除某个测试号):
// 写入前拦截:返回 true 即不记这条流水
add_filter( 'guaqi_download_feed_skip', function ( $skip, $user_id, $post_id ) {
if ( $user_id === 7 ) return true; // 排除某个具体用户
return user_can( $user_id, 'manage_options' ); // 或者排除全部管理员
}, 10, 3 );
2)让管理员也出现在登录/访问播报里(单人站点会想让页面显得更热闹):
add_filter( 'guaqi_download_feed_exclude_admin_noise', '__return_false' );
3)在别的插件里复用同一份流水(比如自己再做一个「下载排行」):
// 表:{prefix}guaqi_download_feed
// 一行 = 一次事件:event_type / user_id / post_id / resource_index / created_at
// 注意 created_at 是 GMT,排序算龄要用 UTC 口径
// 或者挂官方钩子做别的事:
add_action( 'guaqi_count_updated', function ( $post_id, $type ) {
if ( $type !== 'resource' ) return; // 浏览等计数也走这个 action,必须过滤
// 你的逻辑
}, 30, 4 );
4)把播报条塞进别的页面。模块本身不绑定首页,任意页面都能拖,也可以一个页面放多条。
十二、常见问题
Q:装了,但一直是「暂时没有新的动态」。
先打第十节那个接口看 table.byEvent。如果确实全是 0,检查三件事:① 测试时是不是未登录状态(游客不记流水);② 是不是管理员(登录/访问被过滤掉了,用非管理员账号测);③ 你那几次下载是不是被权限或配额拦下来了(被拦的下载不算)。
Q:播报里出现了我自己测试的下载记录。
正常,下载和注册不排除管理员(故意的,测试痕迹留着更真实)。用第十一节的 filter 排除掉,或者等它过了 7 天自动出窗。
Q:只有 1 条的时候看着怪怪的。
1 条时不滚动、静止显示,这是有意设计的——省掉无意义的空转。等池子攒到 2 条以上就正常滚了。
Q:首页变慢了吗?
不该。列表和今日计数共用一份 30 秒缓存,首页自己的渲染不吃库;真实数据是客户端挂载后才拉的。如果你的首页本来就重,先查别的地方。
Q:能显示游客的下载吗?
不能,也不建议。游客没有稳定身份,做出来就是「某位用户下载了」,没有社交证明价值。同理,游客访问也不进播报——签到表里本来就只有登录用户。
Q:访问动态为什么没有具体页面?
官方签到表只存了来源域名、没存页面路径。详见第三节。想记具体页面得在前端另做上报,那是另一个插件的活。
Q:会不会把访客的名字暴露出去?
默认脱敏(z***e),且登录、访问这两类还会额外过滤掉管理员。想更保守:把「显示资源标题」关掉,或干脆关掉某个事件类型。
十三、还能怎么玩
- 配排行榜。同一张表按
post_id聚合就是「本周下载榜」,按user_id聚合就是「下载狂人榜」,按event_type分组就是「今日活跃构成」。数据都是现成的。 - 做「实时热度」角标。某资源 24 小时内被下载超过 N 次,就在列表页给它盖一个热门标记。查表就行,不用改这个插件。
- 注册播报当增长看板。把
register单独聚合,按天画一条注册曲线——比翻后台用户列表直观得多。 - 换前缀文案做氛围区分。考研站写「本站动态」、游戏站写「刚刚发生」、社区写「热乎的」——同一个模块,前缀一改气质就变了。
- 给播报加音效。资源站戏太多会显廉价;但如果是游戏站或直播站,切换时来一声轻响,「活着」的感觉会翻倍。
附录 A:文件清单
guaqi-download-feed-1.4.2.zip 安装包(28874 字节 / 8 个文件)
├─ manifest.json 模块声明(1 个模块 / 2 个 action / 11 项设置)
├─ wordpress/plugin.php 建表升级 / 四类事件采集 / 合并查询 / GraphQL / 90 天清理
├─ node/server/index.mjs action 翻译 + SSR 骨架壳
├─ node/client/components/0/index.js 单行播报组件(零依赖,纯展示)
├─ node/client/components/0/style.css 样式(中性底 + 一枚呼吸灯)
├─ i18n/zh-hans.json 中文词条
├─ i18n/en.json 英文词条
└─ i18n/es.json 西语词条
三语词典必须与代码里的兜底文案逐字一致——宿主缺词条时会原样返回 key,这时候就得靠内置中文兜底,两边不一样会闪一下。
附录 B:数据表与索引
表:{prefix}guaqi_download_feed
event_type 事件类型:download / register / login / visit
user_id 事件主体
post_id 关联资源(注册/登录/访问为 0)
resource_index 该资源里的第几个下载项
frontend_scope 前端作用域(多站点/多域名区分用)
created_at 事件时间(GMT,与官方签到表同轴)
索引
idx_time 按时间窗查
idx_type_time 按事件类型 + 时间查
idx_post_time 按资源聚合(做下载榜)
idx_user_time 按人查(去重与排查)
created_at 是 GMT,为了让两张表能放在同一条时间轴上排序,本表也统一存 GMT,升级时会把老数据平移一次。所谓「今日」则是按站点本地日期算出来的,比较前会先把本地零点换算成 GMT。附录 C:版本演进
| 版本 | 做了什么 |
|---|---|
| 1.0.0 | 初始版本,只播报下载 |
| 1.1.0 | 改成单行滚动播报条;修空数据卡骨架 |
| 1.2.0 | 从「下载动态」扩成四类事件的站点动态流;表加 event_type,时间基准统一为 GMT |
| 1.3.0 | 修升级后下载动态整块消失(补 event_type 归类回填,表结构版本 2 → 3) |
| 1.4.0 | 视觉重做:品牌色锚定方案 |
| 1.4.1 | 修下载条目跳去后端域名(改用官方 slug 映射) |
| 1.4.2 | 明亮模式去品牌色、呼吸灯强化(当前版本) |
安装包:guaqi-download-feed-1.4.2.zip(28874 字节 / 8 文件)。文中数值均取自 wordpress/plugin.php 的常量声明。
- 本地下载已验证可用
可用性由站点定时核验,最近一次 2026-10-12 09:42。若某个下载点不可用,可在评论区告知,我们会尽快替换。
源码安全检测报告
已检测- 请求参数直接进入执行函数未发现
- 请求参数直接进入文件包含未发现
- 变量函数执行请求参数未发现
- 请求参数批量覆盖变量未发现
- 编码载荷后执行(混淆特征)未发现
- 回连裸 IP 地址未发现
- 配置预加载文件(隐蔽后门手法)未发现
- 向文件写入 PHP 代码未发现
- 第三方二次打包 / 转发站标记
wordpress/plugin.php:807包内存在第三方源码站的引流文件、水印页或导流文案。说明这不是原厂原始包,而是被搬运方重新打包过。二次打包是投毒最高发的环节,且无法与官方包比对校验,因此来源可信度必须下调,交付前应逐文件核对。
第三方转发站标记 / 引流文件:wordpress/plugin.php建议:清掉第三方引流文件与标记文件,然后通读一遍被改动过的核心文件,确认除标记之外没有夹带别的改动。
仅「风险项」参与评分,加固建议与来源情报只作呈现