用户要求为「插件开发」版块写一则原创帖子。强制写作角度是:与社区模块联动:发帖、回帖、积分接口对接经验。
段落,语言贴合开发者交流,带关键代码片段。 注意:第一行标题,空一行后是正文。 标题:"帖子从’已发布’滑进回收站,用户积分却没’回吐’:我靠 `transition_post_status` 补上了这条’暗沟’" 正文内容规划:
前阵子给社区做发帖积分插件,一切顺风顺水:用户点发布,积分到账,排行榜刷新。直到运营反馈一个诡异现象——某用户把帖子删了(其实是移入回收站),积分却原封不动留在账户里。更离谱的是,从回收站恢复后,系统居然又给他发了一笔“复活奖励”。
我一查日志,发现问题出在钩子上。最早为了图省事,我在 save_post 里直接判断 $post->post_status == 'publish',是发布状态就加积分。这导致每次点击“更新”或者“移到回收站”再触发保存时,逻辑全乱套。因为 save_post 只告诉你“存完了”,却不告诉你“从哪来、到哪去”。
后来换到 trash_post 和 untrash_post,结果发现这俩钩子只管回收站,对“草稿↔发布”这种来回横跳毫无办法。更头疼的是,积分接口的调用散落在三个地方:发布加、删除减、恢复加,代码像意大利面条。
最后把逻辑统一收进 transition_post_status,才算把状态机盘清楚。这个钩子会给你三个关键参数:$new_status、$old_status、$post。你只要一张状态映射表,就能决定积分是“发放”、“追回”还是“无视”。
核心思路是:只在“有效可见”状态(publish/private)与“无效不可见”状态(draft/trash/auto-draft)之间切换时,才触发积分变动。而且必须做幂等——同一篇文章同一次流转,只许操作一次。
上段关键代码,我把它包进一个状态机对账类里:
add_action( 'transition_post_status', [ $this, 'sync_credit_on_status_change' ], 10, 3 );
public function sync_credit_on_status_change( $new_status, $old_status, $post ) {
// 只处理主文章,排除自动保存/修订
if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
return;
}
$visible = [ 'publish', 'private' ];
$hidden = [ 'draft', 'pending', 'trash', 'auto-draft' ];
$was_visible = in_array( $old_status, $visible, true );
$is_visible = in_array( $new_status, $visible, true );
// 状态没变,不操作(防重复保存)
if ( $old_status === $new_status ) {
return;
}
// 从不可见到可见:发放积分
if ( ! $was_visible && $is_visible ) {
$this->credit_service->grant( $post->post_author, 'publish_post', $post->ID );
return;
}
// 从可见到不可见:追回积分
if ( $was_visible && ! $is_visible ) {
$this->credit_service->revoke( $post->post_author, 'publish_post', $post->ID );
return;
}
}
这里有个细节:grant 和 revoke 内部必须做“业务幂等”。我的做法是在积分流水表里用 (user_id, action, object_id, direction) 做联合唯一索引。同一篇文章同一类动作,要么加、要么减,绝不允许重复插入两条正向记录。
另外,回收站那个“恢复”操作,本质是 trash -> publish,在上面的逻辑里会自动命中 !$was_visible && $is_visible,积分会正常返还。不需要再单独监听 untrash_post。如果运营后台支持“私密转公开”或者“公开转草稿待审核”,逻辑也能一并覆盖。
还有一个隐藏坑:批量操作。后台文章列表里勾三篇文章点“移至回收站”,transition_post_status 会逐篇触发,但如果你在里面做了远程 HTTP 请求(比如调用第三方积分网关),瞬间的并发可能把对方接口打挂。我的解法是把积分变动写成任务,推给 Action Scheduler,让后台慢慢消化,发帖流程本身只负责扔事件进队列。
总结下来,和社区模块(发帖、回收、审核)联动时,千万别在 save_post 里拍脑袋判断状态。transition_post_status 才是 WordPress 留给我们的“状态机钩子”。把积分接口的调用对齐到状态流转上,再配合流水表的幂等约束,基本能杜绝“删帖不还分、恢复再送分”的灵异事件。

