在状态查询时兼顾规则变化和用户操作

2026年11月14日 35点热度

背景介绍

判断对象当前状态,需要同时考虑既定规则和用户操作。用户操作的时机和内容无法预先确定;即使没有新的操作,规则所依赖的条件变化也可能改变判定结果。因此,仅在用户操作时更新并保存的状态标记,可能无法反映查询时的实际情况。本文采用的做法是结合操作留下的信息和查询时适用的规则,统一计算当前的待处理状态。

操作记录是一次操作留下的信息,其中的完成标记表示该次操作对应的处理是否完成。对象可能已在后续操作中完成,旧记录仍保留未完成标记;记录也可能因规则所依赖的条件变化而失效。因此,仅凭旧记录的完成标记,无法判断对象当前状态。

总体方案是在查询时,结合操作记录、对象当前的完成情况和当时适用的规则,按列表顺序选出第一条仍有效且尚未完成的记录,再输出当前是否有待处理对象及对应目标;没有符合条件的记录时,目标为空。本文具体通过查询时间判断有期限的记录是否有效。历史记录可以保留原状,当前结果由这些输入共同计算。

方案对比

当前状态既会因用户操作改变,也会因规则所依赖的条件变化而改变。选择处理方式时,需要明确:哪些变化会触发状态更新,更新由谁负责,查询时是否还需要计算。

围绕这两个变化来源,可以比较三种状态维护与计算方式,见 表1。它们将计算安排在不同的时机,也承担不同的维护责任。

表1 状态维护与计算方式对比

方式 用户操作后如何处理 规则条件变化时如何处理 维护责任与适用边界
操作时更新并保存状态 每次相关操作都更新状态标记及对应目标,查询时直接读取 没有操作就不会更新;适合规则条件在两次操作之间保持不变,或相关变化也能触发更新的情况 查询直接,但所有影响结果的操作入口都须同步更新,避免标记与目标不一致
结合规则变化维护预计算状态 操作后重新计算并保存结果 根据规则依赖安排更新;时间规则可在生效、到期时触发,或定期刷新 适合希望减少查询计算、且能覆盖变化来源的情况;须维护触发、补偿和一致性,定期刷新还存在刷新间隔内的滞后
查询时统一计算 更新操作记录和对象完成情况,查询时结合这些输入计算结果 每次查询都按当时的条件判定;本文具体使用查询时间检查有效期 无须额外保存派生结果,但每次查询都有计算成本;调用方须提供最新输入,并负责重新查询和刷新展示

本文采用查询时统一计算。对象的完成情况和操作记录仍由操作处理逻辑更新,记录是否有效则结合查询时的条件判定。这样,即使没有新的用户操作,查询时间跨过生效或到期边界后,也能得到相应结果,无须为此改写旧记录。这适合当前状态和记录已在内存中、调用方需要同时获得状态标记与操作目标的处理位置。

代价是每次查询都要重新检查记录;记录较多或查询频繁时,需要评估重复计算的成本。调用方还须提供最新的输入,并在需要更新展示时重新查询。时间推进本身不会让函数自动执行,也不会自动刷新界面。

查询时,计算接收以下三类输入:

  • 当前状态 state:数据组标识、处理起点,以及当前已完成对象的索引列表。
  • 操作记录列表 grants:每条记录的所属数据组、对象索引、当次完成标记,以及是否临时、生效时间和到期时间。
  • 查询时间 now:由调用方显式传入,与记录使用相同的时间单位和时间轴。

计算结合当前状态 state 和查询时间 now,从操作记录列表 grants 中按输入顺序选出第一条符合条件的记录,输出“是否有待处理对象”的状态标记和用于定位的对象位置。选中记录时,标记为真,位置取自该记录;没有符合条件的记录时,标记为假,位置为空。展示与定位可共用这次选择的结果;完整界面效果仍需结合实际调用验证。

计算只读取这些输入,不改写历史记录,也不在内部读取系统时钟。给定相同的输入和查询时间,可以重现相同的结果。

关键数据处理

以下处理以已经校验的内存数据为前提。计算按输入列表的顺序逐条检查:记录是否属于当前数据组、是否位于处理范围内、记录与对象是否尚未完成,以及临时记录在查询时是否有效。

图1 展示了顺序遍历与条件分支:先判断是否还有未检查记录,再按输入顺序取下一条;任一条件不满足就继续遍历,选中第一条通过全部检查的记录后立即停止。选中时,输出状态为 true,目标为该记录的对象索引,索引 0 也有效;列表耗尽时,输出状态为 false,目标为 null。完整实现见 Demo 源码。

flowchart TD
    G[/"记录列表 grants"/] --> N{"是否还有未检查记录?"}
    N -- true --> R["按输入顺序取下一条记录"]
    R --> A{"是否属于当前数据组<br/>且对象索引 ≥ 处理起点?"}
    S[/"当前状态 state"/] -.-> A
    S -.-> B
    A -- true --> B{"是否记录未完成<br/>且对象索引不在已完成列表中?"}
    B -- true --> C{"是否满足:非临时<br/>或(at ≤ now < until)?"}
    T[/"查询时间 now"/] -.-> C
    A -- false --> N
    B -- false --> N
    C -- false --> N
    C -- true --> Y["选中当前记录,停止遍历<br/>状态=true,目标=当前记录的对象索引"]
    N -- false --> Z["状态=false<br/>目标=null"]

图1 记录筛选与输出映射

归属与范围检查先确定哪些记录可以参与本次判定。归属检查要求记录的数据组标识与当前数据组一致,避免混入其他组的记录;范围检查要求对象索引不小于处理起点,因此起点本身也在范围内。

完成状态分两层检查。记录的完成标记表示当次操作对应的处理是否完成;对象当前的完成情况则通过已完成索引列表判断。两项都必须是未完成:当次记录已完成时应跳过,对象已在后续操作中完成时也应跳过,即使旧记录仍保留未完成标记。

有效期检查只针对临时记录。它的有效区间是 [at, until):恰好到生效时间时有效,恰好到到期时间时失效。非临时记录跳过时间检查,但仍须通过归属、范围和两层完成状态检查。

多条记录都符合条件时,选择输入列表中最先出现的一条,不按时间或对象索引重新排序。因此,列表顺序直接决定选择的优先级;这里的“第一条”不代表最新记录,也不代表索引最小的记录。

最后,根据是否选中记录生成状态标记,并从选中记录中取得目标索引。索引 0 是有效位置,不能把它当成没有结果;只有未选中记录时,目标才为 null。这样可以明确区分“目标位于第一个位置”和“当前没有目标”。

案例讲解

在领取章节后限时阅读的业务场景下,用户可在到期后重新领取,读完的章节则可永久访问。用户第一次领取某章后未读完便到期,重新领取并读完后,旧领取记录仍保留未完成标记。旧判定检查是否存在未完成的领取记录,以及对应章节是否可访问。由于章节读完后具有永久访问权限,旧记录仍能通过这两项检查,导致已读章节被误报为未读。

采用本文方案后,查询同时检查领取记录的有效性和章节当前的完成情况:旧记录已经过期,章节也已读完,因此不会再被判为未读,历史记录无需改写。未读展示与阅读入口使用统一判定,确定未读标记和目标章节。这里判断的只是已有领取记录的未读状态;若下一章已可领取,界面仍可能显示领取提示。

Demo 源码

xxs9331

这个人很懒,什么都没留下

文章评论