Postgresql+Express+Godot栈下实现发奖即删的概率奖励表方案咨询
方案可行性确认
你的思路完全可行,这是服务端控量发奖的标准实现方案,既可以避免硬编码奖励规则,也能从根源上解决客户端无法限制发放总量、易被篡改的问题。
具体实现流程
1. PostgreSQL 表设计
你可以直接新建奖励池表,参考表结构如下:
CREATE TABLE reward_pool ( id SERIAL PRIMARY KEY, reward_name VARCHAR(100) NOT NULL, -- 奖励名称,用于前端展示 reward_type VARCHAR(50) NOT NULL, -- 奖励类型:金币/道具/皮肤等 reward_param INT NOT NULL, -- 奖励参数:金币数量/道具ID/皮肤ID等 weight NUMERIC(4,2) NOT NULL DEFAULT 1.0, -- 抽取权重,实际抽取概率=单条奖励权重 / 剩余所有奖励总权重 is_valid BOOLEAN NOT NULL DEFAULT TRUE -- 可选,软删除标记,不需要保留历史发奖记录可以删掉这个字段 );
你可以提前把所有要发放的奖励批量导入这个表,后续要调整奖励配置、补充库存、修改概率直接操作表数据即可,不需要修改代码。
2. 原子抽取逻辑(避免超发)
核心要保证「抽取-返回-删除/标记已使用」是原子操作,避免并发场景下同一个奖励被多个用户抽中。PostgreSQL 可以用单条SQL完成全流程,不需要额外加分布式锁:
WITH selected_reward AS ( SELECT id, reward_name, reward_type, reward_param FROM reward_pool WHERE is_valid = TRUE -- 没用软删的话去掉这个条件 ORDER BY -LOG(RANDOM()) / weight -- 按权重随机的标准实现,概率和权重正相关 LIMIT 1 FOR UPDATE SKIP LOCKED -- 高并发场景下避免锁冲突,不会出现超发 ) -- 不用软删就用DELETE,用软删就用UPDATE DELETE FROM reward_pool WHERE id = (SELECT id FROM selected_reward) -- 用软删的话换成:UPDATE reward_pool SET is_valid = FALSE, used_user_id = $1, used_time = NOW() WHERE id = (SELECT id FROM selected_reward) RETURNING *;
这个SQL执行后要么返回抽中的奖励数据,要么返回空(奖励池已空),完全不会出现超发问题。
3. Express 接口逻辑
- 第一步先校验请求合法性:必须验证用户的通关凭证(比如通关时服务端下发的一次性有效token),避免客户端伪造通关请求刷奖励
- 执行上述抽取SQL,如果返回空,直接给客户端返回「当前奖励已发放完毕」的提示
- 如果返回奖励数据,把用户ID、奖励信息、通关时间等写入你已有的用户相关业务表
- 把奖励结果返回给Godot客户端
4. Godot 端适配
客户端不需要做任何奖励计算逻辑,通关后直接请求后端发奖接口,拿到返回结果后展示给用户即可,完全规避客户端篡改的风险。
优化建议
- 如果有多个不同的活动奖励池,可以在
reward_pool表新增activity_id字段区分,不需要重复建表 - 可以用你现有的React栈做一个简易的奖励配置后台,直接可视化调整奖励权重、补充库存、上下架奖励,不用操作数据库也不用发版
- 如果单种奖励的库存很大,不需要每条奖励存一行,可以优化表结构新增
stock字段存储库存,抽取时对应扣减库存即可,节省存储空间
内容的提问来源于stack exchange,提问作者Matthew
相关产品推荐
相关产品推荐

