本文约 4,726 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:写着”满 200 减 30″的券已经失效两周了
做优惠券/返利站,最伤害信任的场景不是没券,而是有券但不能用。用户从搜索进来,复制券码去结算,发现提示”优惠券已过期”,跳出率接近满分。这类站点的内容半衰期极短:今天有效的券明天可能就没了,一天两三百条人工更新几乎是极限,规模做不上去。唯一可行的路线是把数据源接到 API 上,由程序定时刷新 + 过期自动下线,把人从复制粘贴里解放出来做选品与页面质量。
原理分析:券站本质是「定时 ETL + 页面生成」
整个系统分三层。第一层是数据源:电商联盟平台通常提供券/商品接口,例如淘宝开放平台的 taobao.tbk.item.get(淘宝客物料搜索接口),京东联盟的 jd.union.open.goods.query,海外则是 Amazon Product Advertising API v5 或各家 Affiliate Network(如 Awin、CJ Affiliate)的商品与促销 feed。第二层是调度与存储:WordPress 的定时任务 WP-Cron + 自定义数据表,负责拉数据、过滤、落库。第三层是缓存与呈现:Transients API 承载热数据,页面按商品库/品牌/券区/活动专区生成。
需要注意的是,WordPress 默认的 cron 由页面访问触发,纯静态缓存站点可能长时间不跑 wp-cron.php。成熟的解法是在系统 crontab 里定时 wget -q -O - https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1,并在 wp-config.php 里用 define( 'DISABLE_WP_CRON', true ); 关闭默认机制。
| 环节 | 使用方式 | 备注 |
|---|---|---|
| 调度 | WP-Cron + 系统 crontab | 前者易用,后者准时 |
| 存储 | 自定义表($wpdb->prefix . 'mf_coupons') |
量大时别塞 postmeta |
| 缓存 | set_transient() / get_transient() |
没装外部对象缓存时写入 options 表 |
| 失效 | 券 end_time 字段 + 查询过滤 |
不应删除,留作历史 |
保姆级实操
步骤 1:确认数据源资质与调用配额。 国内平台普遍要求先注册开发者、创建应用拿到 appkey/appsecret,并对调用频次做限流;海外 PA-API v5 还要求站点有实际销售才会放款。拿到凭证后先 Postman 跑通一次。
步骤 2:建自定义表而不是堆 postmeta。 券这种几千上万条的数据写进 postmeta 会让 meta_query 慢到无法接受:
register_activation_hook( __FILE__, function () {
global $wpdb;
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
$charset = $wpdb->get_charset_collate();
dbDelta( "CREATE TABLE {$wpdb->prefix}mf_coupons (
id bigint(20) NOT NULL AUTO_INCREMENT,
source varchar(32) NOT NULL,
external_id varchar(64) NOT NULL,
title varchar(255) NOT NULL,
coupon_amount varchar(64) DEFAULT '',
start_time datetime DEFAULT NULL,
end_time datetime DEFAULT NULL,
click_url text,
updated_at datetime NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY src_ext (source, external_id),
KEY end_time (end_time)
) $charset;" );
} );
步骤 3:注册定时任务并写好幂等逻辑。 拉券要做到「重复跑不脏库」,用 upsert(ON DUPLICATE KEY UPDATE):
add_action( 'init', function () {
if ( ! wp_next_scheduled( 'mf_sync_coupons_hourly' ) ) {
wp_schedule_event( time(), 'hourly', 'mf_sync_coupons_hourly' );
}
} );
add_action( 'mf_sync_coupons_hourly', function () {
global $wpdb;
$resp = wp_remote_post( 'https://api.example-network.com/v1/promotions/search', [
'timeout' => 15,
'headers' => [ 'Authorization' => 'Bearer ' . get_option( 'mf_api_token' ) ],
'body' => [ 'page_size' => 100, 'sort_tag' => 'total_sales_des' ],
] );
if ( is_wp_error( $resp ) || 200 !== (int) wp_remote_retrieve_response_code( $resp ) ) {
error_log( '券同步失败:上游不可达' );
return;
}
$rows = json_decode( wp_remote_retrieve_body( $resp ), true );
foreach ( (array) ( $rows['result_list'] ?? [] ) as $item ) {
$wpdb->query( $wpdb->prepare(
"INSERT INTO {$wpdb->prefix}mf_coupons
(source, external_id, title, coupon_amount, start_time, end_time, click_url, updated_at)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
coupon_amount = VALUES(coupon_amount), end_time = VALUES(end_time),
click_url = VALUES(click_url), updated_at = VALUES(updated_at)",
'example', $item['promotion_id'], $item['title'], $item['discount'],
$item['start_time'], $item['end_time'], $item['click_url'], current_time( 'mysql', 1 )
) );
}
delete_transient( 'mf_coupon_cache_home' );
} );
注意这里要用 wp_remote_retrieve_response_code() 取值,传的是 wp_remote_post() 返回的整个响应对象,而不是响应体字符串。
步骤 4:页面读取时先查 transient。 热区数据不直接打数据库:
$cache = get_transient( 'mf_coupon_cache_home' );
if ( false === $cache ) {
global $wpdb;
$cache = $wpdb->get_results(
"SELECT * FROM {$wpdb->prefix}mf_coupons
WHERE end_time > UTC_TIMESTAMP()
ORDER BY end_time ASC LIMIT 20"
);
set_transient( 'mf_coupon_cache_home', $cache, 15 * MINUTE_IN_SECONDS );
}
步骤 5:过期券的处置策略。 不要物理删除,改成查询时过滤 end_time,并对已过期超过 30 天的 URL 返回 410 Gone 或 301 到同类页,避免留下大量死链。WordPress 404 与 301 重定向处理
步骤 6:合规红线。 禁止对券面额度做夸大表述,禁止使用”全网最低””第一”等极限词;返利要与平台规则一致,不得虚构返利比例;页面需说明价格与优惠信息以商家结算页为准。
常见踩坑
- 把
wp-cron.php交给页面访问触发,站点上了静态缓存后任务长期不执行,表现为”券列表一整天没变化”。 - 案例: 某站把券数据全写进
postmeta,三个月后EXPLAIN显示单次查询扫描行数超过百万,列表页 TTFB 从 200ms 涨到 6 秒以上。 set_transient()第二个参数传了数组却忘了过期时间,等价于永不过期,缓存击穿。- 上游 API 返回分页 100 条但只处理了第一页,热门券永远进不来。
dbDelta的KEY行写成了逗号结尾,导致索引未创建,慢查询日志暴涨。- 未处理时区,
current_time()与 API 的 UTC 串混淆,券提前或延后失效。 - 同一个券被多次入库只靠
title去重,导致标题相同、额度不同的券互相覆盖。WordPress 定时任务 WP-Cron 详解
总结
券站能否长期存活,取决于三点:数据是否自动更新且幂等、过期券是否不留聚合死链、表达是否不越合规边界。行动清单:确认 API 配额 → 自定义表 + upsert → 系统级 crontab → transient 缓存 → 过期策略 → 文案合规审查。先把”不出错”做到位,规模是自然结果。
