You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.10 21:09:03