我正在考虑设计一种在我的网站上使用的成就系统的最佳方法。数据库结构可以以最佳方式找到,以告知缺少的3个或更多连续记录,并且该线程实际上是从开发人员处获得想法的扩展。
我在这个网站上经常谈论徽章/成就系统时遇到的问题就是-都是谈论而没有代码。实际的代码实现示例在哪里?
我在这里提出一种设计,希望人们可以做出贡献,并希望为编码可扩展成就系统创建一个好的设计。我并不是说这是最好的,远非如此,但这是一个可能的起点。
请随时贡献您的想法。
我的系统设计思路
似乎普遍的共识是创建一个“基于事件的系统”-每当发生已知事件(如创建,删除帖子等)时,它都会像这样调用事件类。
$event->trigger('POST_CREATED', array('id' => 8));
然后,事件类找出此事件正在“侦听”哪些标志,然后找到requires
该文件,并创建该类的实例,如下所示:
require '/badges/' . $file;
$badge = new $class;
然后,它将调用默认事件,以传递调用时接收到的数据trigger
。
$badge->default_event($data);
徽章
这才是真正的魔术发生的地方。每个徽章都有自己的查询/逻辑,以确定是否应授予徽章。每个徽章的格式如下:
class Badge_Name extends Badge
{
const _BADGE_500 = 'POST_500';
const _BADGE_300 = 'POST_300';
const _BADGE_100 = 'POST_100';
function get_user_post_count()
{
$escaped_user_id = mysql_real_escape_string($this->user_id);
$r = mysql_query("SELECT COUNT(*) FROM posts
WHERE userid='$escaped_user_id'");
if ($row = mysql_fetch_row($r))
{
return $row[0];
}
return 0;
}
function default_event($data)
{
$post_count = $this->get_user_post_count();
$this->try_award($post_count);
}
function try_award($post_count)
{
if ($post_count > 500)
{
$this->award(self::_BADGE_500);
}
else if ($post_count > 300)
{
$this->award(self::_BADGE_300);
}
else if ($post_count > 100)
{
$this->award(self::_BADGE_100);
}
}
}
award
函数来自扩展类Badge
,该类基本上检查用户是否已经获得该徽章,如果没有,将更新徽章db表。徽章类还负责为用户检索所有徽章,并将其以数组形式返回,等等(因此,徽章可以例如显示在用户个人资料上)
何时在已经运行的站点上首次实施该系统呢?
还可以将“ cron”作业查询添加到每个徽章。这样做的原因是因为在首次实施并初始化徽章系统时,应该已经获得的徽章尚未授予,因为这是基于事件的系统。因此,按需运行每个徽章的CRON作业,以奖励所需的任何东西。例如,上面的CRON作业如下所示:
class Badge_Name_Cron extends Badge_Name
{
function cron_job()
{
$r = mysql_query('SELECT COUNT(*) as post_count, user_id FROM posts');
while ($obj = mysql_fetch_object($r))
{
$this->user_id = $obj->user_id; //make sure we're operating on the right user
$this->try_award($obj->post_count);
}
}
}
随着上述cron类扩展了主要的badge类,它可以重新使用逻辑功能 try_award
我为此创建专门查询的原因是,尽管我们可以“模拟”以前的事件,即遍历每个用户帖子并触发事件类,因为$event->trigger()
这会非常慢,尤其是对于许多徽章。因此,我们改为创建优化的查询。
什么用户获得奖励?关于根据事件奖励其他用户
该Badge
级award
功能作用user_id
-他们总是会给予奖励。默认情况下,会将徽章授予导致事件发生的人员,即会话用户ID(对于此default_event
功能,这是正确的,尽管CRON作业显然遍历所有用户并授予单独的用户)
因此,让我们举个例子,在一个编码挑战网站上,用户提交了他们的编码条目。管理员然后判断输入,完成后将结果发布到质询页面,以供所有人查看。发生这种情况时,将调用POSTED_RESULTS事件。
如果您想为所有发布的条目授予用户徽章,可以说,如果它们在前5名中名列前茅,则应该使用cron作业(尽管请记住,这将为所有用户更新,而不仅仅是针对挑战结果发布了)
如果您想针对一个特定区域进行cron作业更新,让我们看看是否有一种方法可以将过滤参数添加到cron作业对象中,并获得cron_job函数来使用它们。例如:
class Badge_Top5 extends Badge
{
const _BADGE_NAME = 'top5';
function try_award($position)
{
if ($position <= 5)
{
$this->award(self::_BADGE_NAME);
}
}
}
class Badge_Top5_Cron extends Badge_Top5
{
function cron_job($challenge_id = 0)
{
$where = '';
if ($challenge_id)
{
$escaped_challenge_id = mysql_real_escape_string($challenge_id);
$where = "WHERE challenge_id = '$escaped_challenge_id'";
}
$r = mysql_query("SELECT position, user_id
FROM challenge_entries
$where");
while ($obj = mysql_fetch_object($r))
{
$this->user_id = $obj->user_id; //award the correct user!
$this->try_award($obj->position);
}
}
即使未提供该参数,cron功能仍将起作用。