游戏化与邀请

当使用游戏化插件时,积分会奖励给已兑换的邀请。
这很容易被滥用,通过发送邀请并使用重复(虚假)帐户兑换它们。
当管理员删除这样一个虚假帐户时,游戏化插件(和邀请系统)的行为取决于邀请的发送方式。

  • 如果邀请与特定的电子邮件地址相关联,并且用户被删除,则invite记录和user_invite记录都会被硬删除(hard delete)。下次 Sidekiq UpdateScoresFor* 作业运行时,游戏化插件将扣除再次奖励的分数。

  • 如果邀请未与特定的电子邮件地址相关联,并且用户被删除,则invited_users记录将被删除,但invite记录将保留。redemption_count值不会减少。从一个角度来看,这很有意义,因为邀请兑换已经发生,即使该用户后来被删除。但是游戏化插件使用redemption_count来计算分数,因此积分将不会被扣除

在调试这个问题的同时,我还发现积分作业只会考虑少于 10 天的邀请。因此,如果邀请在 11 天后被兑换,则根本不会奖励积分。我本想说“我想它需要查看updated_at而不是created_at”,但是当邀请的兑换计数增加时,updated_at时间戳不会被触及。

11 个赞

像这样是否会更好一些(已调整为适合可计分查询格式 - 这是数据浏览器的测试):

-- [params]
-- date :start_date
-- date :end_date

SELECT 
    invited_by_id AS user_id,
    COUNT(*) AS user_invites,
    COUNT(*) * 10 AS invite_score
FROM invited_users iu
  JOIN invites i ON i.id = iu.invite_id
  JOIN users u ON u.id = iu.user_id
WHERE iu.redeemed_at::date BETWEEN :start_date AND :end_date
  AND iu.user_id <> i.invited_by_id 
  AND u.created_at > iu.redeemed_at
GROUP BY invited_by_id
ORDER BY user_invites DESC

当用户被删除时,他们将从 invited_users 表中清除,因此不再计入计数。如果删除发生在 10 天内,则会自动更正,如果超过 10 天,则需要手动刷新分数。

使用 redeemed_at 日期将计入那些创建时间超过 10 天的邀请。

AND iu.user_id <> i.invited_by_id 也会排除自我邀请。

连接 users 表并添加 AND u.created_at > iu.redeemed_at 也会排除邀请现有用户。

3 个赞

这会是一个好方法,除了有一点:

这并不奏效。有时用户创建的时间比兑换发生的时间稍早。不知道为什么。大多数是十分之几秒,但我也发现了一些十分之几秒的差异。

在真实数据库上测试。

select
  iu.redeemed_at iu_AS redeemed_at,
  u.created_at AS u_created_at,
  u.created_at > iu.redeemed_at AS u_created_gt_iu_redeemed
from invited_users iu
left join users u on u.id = iu.user_id
where iu.redeemed_at is not null
order by iu.id desc;
       iu_redeemed_at       |        u_created_at        | u_created_gt_iu_redeemed
----------------------------+----------------------------+--------------------------
 2023-09-08 00:00:47.557057 | 2023-09-08 00:00:48.376446 | t
 2023-08-25 20:09:03.486362 | 2023-08-25 20:09:03.201357 | f
 2023-08-15 23:38:32.271709 | 2023-08-15 23:38:33.570299 | t
 2023-08-14 10:44:34.19912  | 2023-08-14 10:44:35.429371 | t
 2023-08-12 13:41:10.428013 | 2023-08-12 13:41:11.733973 | t
 2023-07-31 17:58:13.511289 | 2023-07-31 17:57:50.427111 | f
 2023-07-23 00:56:33.455185 | 2023-07-23 00:55:47.999263 | f
 2023-07-19 08:42:44.908096 | 2023-07-19 08:42:46.040201 | t
 2023-06-30 09:11:38.829692 | 2023-06-30 09:11:39.618586 | t
 2023-06-30 08:37:02.322192 | 2023-06-30 08:37:03.133769 | t
 2023-06-29 16:24:01.705616 | 2023-06-29 16:24:02.55067  | t
 2023-06-29 12:53:33.245688 | 2023-06-29 12:53:34.067159 | t
1 个赞

这很有趣。我绝对没考虑到这一点。 :slight_smile:

添加一个小的缓冲区,例如 AND u.created_at + INTERVAL '1 SECOND' > iu.redeemed_at,是否能在不产生太大影响的情况下弥补这一点?

我觉得这有点太“黑魔法”了,因为我们不知道是什么导致了延迟。

我怀疑延迟是由一个大型的 sidekiq 队列或类似的东西引起的,虽然大多数延迟在 1 秒以内,但大约 3% 的延迟在 0:00:01 - 0:15:00(1 秒到 15 分钟)之间。

而且有百分之零点五的延迟长达数天,这似乎是我们试图阻止的那种滥用行为。所以虽然这很有效,但由于误报的数量,它弊大于利。

2 个赞

这应该能解决问题:

1 个赞

这感觉更像是功能请求,而不是错误。(抄送 @Falco

此主题已在 3 天后自动关闭。不再允许回复。