如何处理复杂的用户状态?

3
我的应用程序涉及用户付款。在公司中,该用户具有以下状态:
- 合规的(用户迄今为止已支付所有债务) - 逾期/违约(用户注册至少3个月并且未支付至少1笔债务) - 不活跃(用户注册不到3个月并且未支付任何债务)
在应用程序内的多个位置(和规则)中处理这些规则的最佳方式是什么?
我需要像 `status_id` 这样的字段以及每小时更新它的 cron 作业吗?
没有 `status_id` 字段,并在每个需要显示状态的查询中编写 SQL 规则?
加载一个 `User` 模型并调用一个带有规则的 `->status()` 方法?在这种情况下,如何显示“合计”,例如:我们有3000个逾期用户,15000个不活跃用户等...
这让我头疼了几个月,我真的需要帮助 haha。我们目前有一个解决方案,但它太复杂了。由于似乎这是与处理付款的应用程序中常见的事情,必须有一种更简单的方法来做到这一点:P
谢谢!
注:
- 应用程序当前有90,000个用户 - 我们需要实时获取此信息。 - 此信息用于生成图表的报告。 - 此信息显示在用户配置文件中。 - 此信息显示在列表中。 - 当用户在这些状态之间更改时,会通知用户(例如,在“逾期”状态下输入“您有债务”)。 - 此信息不由应用程序用户管理。 - 需要跟踪状态。
3个回答

3
如果您在多个地方使用此字段,则应将状态存储在单个位置并根据需要进行更新(我还会保留状态的历史记录,但这是另一回事)。如果状态由于某些用户操作(例如正在处理付款)而更改,则可以在该操作上使用触发器。但是,您的状态更改似乎基于事件之后的时间。在这种情况下,您应该运行定期计划任务(作为cron作业或数据库事件)。我有点困惑为什么您要每小时执行一次此操作。似乎每天一次最合适。如果“债务”在任意时间支付,则付款过程应更新状态。对于状态的降级,每天运行一次的单个作业应该足够。

“每小时”只是更新过程运行频率的示例。是的,状态是根据用户操作更新的,而不一定是事件发生后的某个时间,因此更新将是即时的。您的解决方案很棒,我正在与@Chris的解决方案结合使用,感谢您的帮助! - Felipe Francisco

1
我认为这个问题有多种解决方案。 我建议不要有任何定义的状态。从我的观察中,您可以根据其他一些数据“找出”当前状态。例如,“用户迄今为止已经支付了所有债务”。这是通过分析给定期间内发生的所有更改来得知的。您可以聚合数据以找出所需的所有信息。因此,您根本不需要保存状态。它只是从特定时间段内发生在客户帐户上的所有更改中派生出来的。 对于总计也是如此。您可以在数据库级别轻松完成此操作,甚至可以使用一些基于文档的数据库或ElasticSearch。 当然,这假设您跟踪历史更改。如果是这样-问题解决了。如果没有-您必须将状态保存到数据库中,并且将无法获取历史数据。

今天应用程序按照您的描述运行。每次需要时,它会“计算”出状态。问题在于:“计算”过程涉及约4~5个连接,因为我还需要知道像“未支付的债务是否来自处于活动状态的订单”这样的信息。此外,某些用户类型可能会收到与债务本身无关的不同状态。今天,我们有一个SQL块来“解决”状态,但是它很烦人,难以维护并插入到我们的查询中。您在技术上建议如何处理此问题而不保存状态?我对此非常感兴趣。 - Felipe Francisco
@FelipeFrancisco,当你已经有了用户列表后,可以很容易地通过发出第二个查询来完成它。然后在返回之前合并数据。这样,你就拥有了一个单一的真相点。你仍然需要记住添加状态细节,但这可以在对象级别和测试中轻松处理。 - Aleksander Wons

1
有趣的问题,但并没有一个单一的答案。
我认为这里的复杂性可能来自于周围的代码,而不是核心业务逻辑和要求。我这样说是因为三种状态类型,全部都来自于你的内部应用程序,这并不算太糟糕。
一个可能的解决方案,我假设有一定程度的MVC或类似的结构。
考虑到你的模型,user,并扩展ORM(如Eloquent),我将使用Laravel中的Eloquent,因为我最熟悉它,但任何ORM都可以工作。
use Illuminate\Database\Eloquent\Model;
use App\DebtCollector;

public class User extends Model
{
    // Assuming model has the following fields
    // id, status, registration_date, and a one to many
    // relationship with debts 

    protected $fillable = [
        'raw_status',
        'registration_date',
    ];

    public function debts()
    {
        return $this->hasMany(Debt::class);
    }

    public function refreshStatus()
    {
        $dc = new DebtCollector();

        // Business logic inside the "DebtCollector" class
        $this->raw_status = $dc->resolveStatus($this->debts, $this->registration_date);

        // Save value to the underlying datebase
        $this->save();            
    }

    // If you fetch a status directly, it will refresh first, 
    // then return the value
    // 
    public function getStatusAttribute()
    {
        $this->refreshStatus();
        return $this->raw_status;
    }
}


// Schedule task somewhere - ran nightly, or whenever
// 
// This way you can refresh the status only on certain groups
// of data - for example, if the business flow means that once
// they become compliant, they can't go back, there is no need
// to refresh their status anymore 
//
User::where('raw_status', '<>', 'compliant')->refreshStatus();

// Alternatively, the schedule could chunk results and do an update
// only to those not updated in the last 24 hours
//
$date = new DateTime;
$date->modify('-24 hours');
$formatted_date = $date->format('Y-m-d H:i:s');
User::where('last_updated', '>', $formatted_data)->refreshStatus();

“不错嘛”,太好听了哈哈哈 :P。就这样!调用 refreshStatus 并构建一个 StatusResolver 类/方法是正确的方式。非常感谢!我将结合 @Gordon 的历史数据和您的答案,希望我们不需要再进行重构。 - Felipe Francisco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接