V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
zero47
V2EX  ›  程序员

微信读书的书币逻辑是怎样做到独立过期的呢?

  •  1
     
  •   zero47 · 2024-04-19 10:21:56 +08:00 · 6119 次点击
    这是一个创建于 371 天前的主题,其中的信息可能已经有所发展或是发生改变。
    有点好奇微信读书的书币系统是怎么实现的。微信读书每天阅读可以获得书币奖励,基本每天能领取 1 到 2 个,而这些领取的书币都有独立的有效期,貌似一个月。如果每笔领取记录都单独过期,意思是一个月可能会有 30 多笔记录。假设每笔都是 1 个书币,难道买本 30 块的电子书要更新 30 条记录?每次查看余额都要 sum 一下记录?
    46 条回复    2024-04-22 09:36:30 +08:00
    yefuchao
        1
    yefuchao  
       2024-04-19 10:29:58 +08:00
    余额就是一个数字,过期时间到了给你扣掉不就好了
    NoOneNoBody
        2
    NoOneNoBody  
       2024-04-19 10:31:34 +08:00   ❤️ 1
    硬件足够的话,数据越细越好
    不需要每次计算,缓存,或者保存预计算结果就可以了
    zero47
        3
    zero47  
    OP
       2024-04-19 10:33:07 +08:00
    @yefuchao 不能吧,他付款肯定是先用旧的奖励记录,那就必须每笔记录是否被消费都记
    Donjote
        4
    Donjote  
       2024-04-19 10:33:14 +08:00
    更新 30 条记录也没啥吧,余额可以存另外一个表里
    Subfire
        5
    Subfire  
       2024-04-19 10:33:56 +08:00   ❤️ 1
    跟游戏开发中的道具类似, 每次获得的书币道具, 只是 configId 一样, 但是 instanceId 是新增的, 不同 instanceId 的道具都有独立的有效期. 扣减书币道具的时候, 优先扣减即将到期的
    xxxaadsdss
        6
    xxxaadsdss  
       2024-04-19 10:34:42 +08:00
    每次领取都是一条领取记录。领取的商品有过期时间。到时间了。减一下总表的 书币数就好了
    zero47
        7
    zero47  
    OP
       2024-04-19 10:36:35 +08:00
    想了想还有一个情况是,30 块的交易,用户有 29 个 1 块书币奖励和 1 个 2 块书币奖励,还得在最后一个 2 块奖励里记录消费了 1 块,还有 1 块没被消费,这太不优雅了吧
    zero47
        8
    zero47  
    OP
       2024-04-19 10:38:20 +08:00
    @xxxaadsdss 这只能解决 sum 的问题,实际消费还得一条一条的更新奖励记录
    NessajCN
        9
    NessajCN  
       2024-04-19 10:53:19 +08:00   ❤️ 4
    参考比特币的区块嘛
    做一个表,只记录交易信息,譬如 xxxx(时间戳) 入账 1 币,一个月后该条交易记录失效
    消费的时候就是手动将依然在有效期内的前 3 条 1 币交易记录失效,如果是入账 3 币只消费 2 币的记录就失效掉 3 币的记录重新生成一条基于原时间戳的 1 币记录
    余额计算就是简单的所有有效入账记录求和
    zero47
        10
    zero47  
    OP
       2024-04-19 10:58:47 +08:00
    @NessajCN 是的,就是觉得这逻辑太重了,在微信读书这种大体量用户下,这个独立过期逻辑有点自己坑自己的意思
    NessajCN
        11
    NessajCN  
       2024-04-19 11:00:46 +08:00
    @zero47 不重啊......你自己试一下就知道了,没啥计算量的,也就 IO 多一些。不过这种程度的 IO 相对微信本身那就是毛毛雨了
    Rickkkkkkk
        12
    Rickkkkkkk  
       2024-04-19 11:04:26 +08:00
    每条领取记录都是库里一条记录
    定期跑离线任务去库里把数据都过期掉
    算余额会把库里的值全部加起来


    这里会出现几个问题:
    跑全量任务更新过期会不会太重了? (记录很多, 真正要过期的很少)
    每次算余额要把所有的记录加起来会不会慢查询?

    (留为作业吧)
    Fish1024
        13
    Fish1024  
       2024-04-19 11:06:10 +08:00
    领取的时候就写入了这些币的过期时间,到时间自动过期了。
    tomatocici2333
        14
    tomatocici2333  
       2024-04-19 11:08:40 +08:00
    @Fish1024 我感觉也是 写个定时任务扫描过期
    caotian
        15
    caotian  
       2024-04-19 11:08:43 +08:00   ❤️ 5
    之前做一个简单的系统想积分带过期功能,硬着头皮做了类似的方案,基本也实现了需求,结果最后做到退款功能时,还是给整破防了,因为还要考虑退款时退积分,退的积分要根据退款金额计算,退的积分还要保持原来的过期时间,还要考虑退款时,退回的积分有可能已经过期了,过期的积分是直接过期,还是根据规则折算成新积分,感觉本来一个简单的功能越做越复杂,最后索性不做积分过期功能了
    deltadawn
        16
    deltadawn  
       2024-04-19 11:20:49 +08:00   ❤️ 2
    按每月存 1 条记录,初始值为 32 个 0 ,签到的时候把当前天数那一位改成获得的金币数,过期只要把上个月当前天那一位改成 0 ,扣钱的时候,从上个月当天开始循环减。这样数据库操作就少了
    jookr
        17
    jookr  
       2024-04-19 11:26:56 +08:00
    先进先出呗。
    先获取的,先到期/先使用
    ashuai
        18
    ashuai  
       2024-04-19 11:29:06 +08:00
    balance 另有个 detail 表,独立过期时间就行了,不是啥麻烦事
    nqlair
        19
    nqlair  
       2024-04-19 11:35:51 +08:00
    存成 sortedMap ,key 是过期时间,value 是积分,每次登录把过期的删除,积分就是未过期的所有 value 的和,使用时候优先扣过期时间近的
    tinytoadd
        20
    tinytoadd  
       2024-04-19 11:42:35 +08:00
    使用书币是先用快过期的还是先用新领取的。
    zero47
        21
    zero47  
    OP
       2024-04-19 12:29:51 +08:00
    @caotian 我也觉得短期过期太复杂了,话费信用卡的积分都一年清算一次
    qeqv
        22
    qeqv  
       2024-04-19 12:40:36 +08:00
    感觉最简单的方法就是你正文提到的了,别的逻辑都很复杂。
    1. 购买时更新 30 条记录也没什么
    2. 总额可以做缓存,余额变动时更新
    3. 1 个 2 块书币奖励可以设置成两条 1 书币记录
    futuretech6
        23
    futuretech6  
       2024-04-19 12:42:38 +08:00
    感觉是类似 NFT 的实现,每个 token 有自己的 id 和过期时间,然后整体也会维护一个 token 数量信息
    runzekk
        24
    runzekk  
       2024-04-19 13:08:56 +08:00
    sum 也没什么问题,冗余存也没什么问题,量不大,没有大事物都不是问题
    444571840
        25
    444571840  
       2024-04-19 13:28:29 +08:00
    如果要实现的话要挺容易的,数据库一个字段就能解决。
    记录的时候 [获得书币 A ,到期时间戳 1 。获得书币 B ,到期时间戳 2 。xxx],每次要写入新数据的时候,先判断是否有已经到期的书币,删除了,再在字段后面添加(新活动书币,新到期时间戳)即可。
    客户端每次要展示的时候,根据当前时间戳把数据库未到期的 sum 一遍就好了。
    其他查历史的话看 log 就好。
    mrgeneral
        26
    mrgeneral  
       2024-04-19 13:36:12 +08:00
    总和可以是单独的数据,过期是定时任务就能解决,流水信息可以定期归档,实际数据量不大。
    janus77
        27
    janus77  
       2024-04-19 13:50:37 +08:00
    领取时一条一条更新
    使用时只需要更新一下总数字就行了,只有一次啊
    hanbin
        28
    hanbin  
       2024-04-19 14:11:12 +08:00   ❤️ 2
    两个表:

    CREATE TABLE PointsRecord (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '自增 ID',
    user_id INT NOT NULL COMMENT '用户 ID',
    points_amount INT NOT NULL COMMENT '积分数量',
    receive_time TIMESTAMP NOT NULL COMMENT '领取时间',
    expiration_time TIMESTAMP NOT NULL COMMENT '过期时间',
    status VARCHAR(20) NOT NULL COMMENT '状态',
    receive_source VARCHAR(50) NOT NULL COMMENT '领取来源',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
    ) COMMENT '积分领取记录表';

    CREATE TABLE PointsWallet (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '积分钱包 ID',
    user_id INT NOT NULL COMMENT '用户 ID',
    points_balance INT NOT NULL DEFAULT 0 COMMENT '积分余额',
    total_points INT NOT NULL DEFAULT 0 COMMENT '积分总额',
    used_points INT NOT NULL DEFAULT 0 COMMENT '已用积分总额',
    expired_points INT NOT NULL DEFAULT 0 COMMENT '已过期积分总额',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
    ) COMMENT '积分钱包表';

    领取积分:

    PointsRecord 入库一条记录,PointsWallet 更新 total_points & points_balance ,事务处理

    过期积分:

    PointsRecord 更新一条数据,PointsWallet 更新 total_points & points_balance & expired_points ,事务处理

    积分消费:

    PointsWallet 更新 used_points & points_balance

    过期积分用脚本处理

    积分消费时判断余额够就允许消费。
    hanbin
        29
    hanbin  
       2024-04-19 14:15:15 +08:00
    @hanbin 将积分领取和积分消费解耦,消费时不关心积分的有效期属性,积分的有效属性也不关心消费的具体场景,只关心额度变化即可。
    MoYi123
        30
    MoYi123  
       2024-04-19 14:25:35 +08:00
    @zero47 这点数量算什么? 随便一个网络游戏, 一次就加载几千个装备物品, 还不是大把人能做?
    pkoukk
        31
    pkoukk  
       2024-04-19 15:03:02 +08:00
    两个表,一个当前的 sum ,一个 detail
    通过事件系统,detial 表的变动通知到 sum ,sum 改余额就完事了
    而且一天才一两条,不用事件系统,搞定时任务扫都绰绰有余
    NizumaEiji
        32
    NizumaEiji  
       2024-04-19 15:17:15 +08:00
    流水过期
    handpr
        33
    handpr  
       2024-04-19 15:27:19 +08:00
    类似物流的.出入库. 入库明细-》 spu,sku,批次,效期次,入库数量
    ZnductR0MjHvjRQ3
        34
    ZnductR0MjHvjRQ3  
       2024-04-19 15:30:45 +08:00
    @zero47 有没有一种可能 实际上都是 1
    ZnductR0MjHvjRQ3
        35
    ZnductR0MjHvjRQ3  
       2024-04-19 15:32:01 +08:00
    @deltadawn 有道理
    kaedea
        36
    kaedea  
       2024-04-19 15:38:31 +08:00 via Android
    你在查找的是不是:数据库
    somebody1
        37
    somebody1  
       2024-04-19 17:38:39 +08:00
    @deltadawn
    补充思路,可以是 32 个 0-9 ,a-z ,这样每天获取到的就可以非常多了,只需要计算一个 32 的字符串,就可以算出来了。

    但是缺点有很明显,微信读书可以做到分钟级别的失效,这个就做不到了。
    yanliu
        38
    yanliu  
       2024-04-19 19:28:51 +08:00
    以我浅见,这种余额类的大都是事件溯源的,即你看到的余额不是简单的存储了一个数字,而是由交易记录实时计算出来的,然后定期合并一个快照,减少计算量,那么就可以在交易记录上打标记来实现过期。这是最常用,也是最安全的做法。当然,也有简单的方案,比如将这类会过期的书币存入 redis 的 zset 中,然后将过期时间戳作为 score ,就可以使用 ZRANGEBYSCORE 之类的命令来返回当前时间和过期时间之间的数据,求和就行了。
    yanliu
        39
    yanliu  
       2024-04-19 19:38:24 +08:00
    @yanliu 一切都要看业务。如果你有 15L 说的那样情况,或者其它情况,那么事件溯源,就是这一类问题目前的唯一解,同时兼顾了安全性和灵活性,并且快照设置合适的话,并发性也不差。
    cellar
        40
    cellar  
       2024-04-19 19:46:47 +08:00
    又不是严格的金融系统。。要么弄个 job ,过期的全删了,要么 where 条件永远带个时间条件就是了。。
    资源有的是的话弄个 redis...
    whileFalse
        41
    whileFalse  
       2024-04-19 21:40:46 +08:00 via Android
    余额会单独存储,每次买书同时更新余额和 30 条消费记录
    DB 性能很强的,现在的硬件太牛逼了
    Znemo
        42
    Znemo  
       2024-04-19 23:54:05 +08:00
    @somebody1 多几个字段,都是 32 个 0-9 ,分别表示个、十、百、千、万位,这样每天的数量取就没有上限了。
    sherryqueen
        43
    sherryqueen  
       2024-04-20 15:47:53 +08:00
    每天记录多加一个余额和状态字段. 消费前拉出所有可用余额, 找出要扣除的记录更新对应状态和余额就好了吧?
    sherryqueen
        44
    sherryqueen  
       2024-04-20 15:50:32 +08:00
    至于总余额 临时算或找个地方 cache 下. 每条记录的过期的话, 定时任务或找个用户查询/消费的时候进行一次统一计算就行.但用户也就是一次批量查询 + 一次批量更新. 计算量也不大
    flyingghost
        45
    flyingghost  
       2024-04-22 01:09:02 +08:00
    大体量用户❎
    蝇量级计算✅

    数字产品没有库存问题,所以各用户间购买行为无关,自己消费自己的就行。
    购买行为不涉及用户间的借款、代付,所以自己算自己的账就行。
    每个用户独立计算自己的币和消费过程,总共百条书币记录,0.1TPS 的操作,单用户放文件存储都不怕效率低。
    过期状态更新只存在于查看时、消费时,依然百条书币记录,0.1TPS 的操作。

    先不考虑其他功能需求,单说你的问题来看,这简直和大体量没关系啊。。。
    唯一和大体量有关的是,如何在亿级用户中找到这个用户独立的账户存储空间。
    lazyfighter
        46
    lazyfighter  
       2024-04-22 09:36:30 +08:00
    可以算一下, 我们默认存储金币记录 1 年, 假设所有用户每天全部签到 365 ,假设共 2 亿用户,那么 2 年的金币记录就是:2*2*365 亿,假设分 128 个表,每个表就是 6 亿左右,所以目前来看存储不是大问题, 那么问题来了就是过期怎么搞,单纯的定时任务去扫这几百亿的数据很扯,所以分开看线上用户实时查看: 实时算每个用户请求自己实时算生效的金币。运营数据统计: 离线任务
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2698 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 11:45 · PVG 19:45 · LAX 04:45 · JFK 07:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.