积分墙被薅秃之后,我才懂"发帖回帖接口"不能只验签名还要验人性

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 9 浏览 0 回复

上周凌晨两点,我后台的积分流水突然炸了。不是那种温和的线性增长,是垂直起飞——十分钟刷了四千多条"发帖成功"记录,积分余额跟着坐了火箭。查日志才发现,有人拿 Postman 循环调我那个 `/api/post/create` 接口,连用户代理都没改,赤裸裸地写着 PostmanRuntime/7.32

这事给我上了一课:社区模块对接,防君子那套在薅羊毛党面前就是纸糊的。今天把发帖、回帖、积分三个接口的"防秃"改造摊开聊聊,全是血泪换的。

一、发帖接口:从"验签名"到"验行为轨迹"

我最初的发帖接口只做了三件事:验 JWT、验签名、插数据库。看起来铜墙铁壁对吧?漏了最关键的行为校验。

现在我的接口进门先查"冷却池"——Redis 里用 user_id:action 做 key,发帖间隔硬卡 15 秒。但这个被绕通过:对方换了十个账号轮询。于是加了第二层:同 IP 下活跃用户数的滑动窗口,5 分钟内超过 3 个不同 uid 发帖,直接进人工审核队列。

最狠的一刀是"内容指纹"。我用 SimHash 把帖子标题和内容压成 64 位签名,新帖进来先和最近 100 条比对,汉明距离小于 3 的,大概率是脚本批量生成的垃圾内容。这招把机器发帖的存活率从 90% 压到了 7%。

二、回帖接口:树形结构的"幽灵回复"陷阱

回帖比发帖阴险在嵌套。我接的是经典 parent_id 自关联模型,早期接口里 parent_id 传什么就挂什么,结果被插了一条 parent_id 指向不存在的楼层,前端渲染递归直接栈溢出。

现在的回帖接口做了"存在性+层级"双校验:先锁行查 parent_id 是否存在,再算当前回复的深度——超过 5 层强制转平铺,防止恶意嵌套撑爆内存。还有个暗坑:有人故意把 parent_id 指向自己,造出闭环。加了个简单的祖先链查询,出现循环引用直接 400。

积分发放这里我拆成了异步队列。回帖成功先写消息到 Redis Stream,消费者那边再做积分计算、等级判断、通知推送。好处是即便积分逻辑崩了,回帖本身不会丢;坏处是用户会喊"我回了帖积分没到账"——所以在接口响应里加了 pending_task_id,前端轮询状态,比直接同步等 MySQL 事务提交稳得多。

三、积分接口:从"加数字"到"审计链"

积分系统被薅那次,我查账发现最离谱的一条:同一个 uid 在 0.3 秒内连续触发"每日登录奖励"六次。原因是客户端网络抖动,用户狂点登录按钮,我后端没做幂等,JWT 也没过期,六次请求全过了。

现在积分变动接口强制带 client_nonce,服务端用 uid:nonce 做 24 小时幂等键。同时每笔积分变动写两条记录:一条明细表给用户看,一条审计表给后台看,审计表带操作前的余额快照。之前有个站长朋友积分对不上账,查了三小时发现是并发扣减时 UPDATE 没走乐观锁,两个请求同时读到 100 分,各扣 80,余额变 -60。现在我的积分扣减全走 Redis Lua 脚本,原子读改写,MySQL 只负责最终对账。

四、和社区主站联动的"暗协议"

这些接口不是孤岛。我搞了个"社区事件总线":发帖成功抛 post.created,回帖抛 reply.created,积分变动抛 credit.changed。主站监听这些事件做联动——比如用户在我的子站发了技术帖,主站首页的"最新动态"组件自动聚合展示,但展示前会回调子站接口验帖子状态,防止删帖了还挂在首页。

这个回调被我设计成"挑战-应答"模式:主站带时间戳和随机串来,子站用预共享密钥算 HMAC 回传,比单纯白名单 IP 灵活,毕竟现在 CDN 节点 IP 池子天天变。

最后

改完这套,我的接口 QPS 没涨,服务器负载反而降了 40%——因为挡住了 90% 的无效流量。社区模块对接这事,前期多写十行校验代码,后期少熬十个通宵。你们接口被薅过吗?用的什么招?

评论0
回复 · 0
还没有回复
微信客服 微信客服