Zsens Admin 插件配置页:我把 `saveConfig` 拆成"三阶段提交"后,终于治好了配置丢失与缓存脏读

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
插件开发 59 浏览 0 回复

之前写后台配置页,总觉得 `saveConfig()` 是个黑盒——表单 POST 过去,成功就刷新页面,失败就弹个红字。直到有次运营同事反馈:刚保存的阈值配置,刷新页面又变回老数值,清浏览器缓存没用,过两分钟自己好了。

排查下来是并发下的缓存穿透 + 异步刷新 race condition。两个管理员同时改配置,A 先写库、B 后写库,但 B 的缓存刷新请求先到达,A 的缓存刷新后到达,最终数据库是 A 的值,缓存却是 B 的值。更坑的是,某些框架的缓存组件默认用 `file` 驱动时,`mtime` 精度只有秒级,1 秒内多次写入直接判定"无需更新"。

后来我干脆把配置保存拆成三阶段提交,把"写库"和"刷缓存"解耦成显式步骤,而不是依赖框架的魔法方法:

// 阶段一:预校验 + 版本锁生成
public function prepareSave(array $input): ConfigDraft
{
    $draft = new ConfigDraft();
    $draft->payload = $this->validator->stripTags($input); // 先清洗
    $draft->version = $this->meta->bumpVersion();          // 原子自增
    $draft->lockKey = 'config:lock:' . $draft->version;
    
    // 5 秒排他锁,防止同一管理员连点
    if (!$this->cache->add($draft->lockKey, 1, 5)) {
        throw new ConcurrentEditException('操作太频繁,请稍后再试');
    }
    
    return $draft;
}

// 阶段二:事务写库,带乐观锁校验
public function commitSave(ConfigDraft $draft): bool
{
    return $this->db->transaction(function () use ($draft) {
        $affected = $this->configModel
            ->where('group', $draft->payload['group'])
            ->where('version', '
评论0
回复 · 0
还没有回复
微信客服 微信客服