You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SvelteKit+Supabase应用中实现存储配额校验与用量更新?

存储配额校验与原子更新的最佳实践(SvelteKit + Supabase场景)

针对你的场景,数据库层面的原子操作是解决并发上传竞态问题的核心,以下是具体的最佳实践方案:

一、推荐方案:Postgres函数+触发器(彻底解决并发问题)

Supabase的storage.objects表支持自定义触发器,只是需要手动编写Postgres逻辑,具体步骤如下:

1. 创建配额校验与更新函数

编写PL/pgSQL函数,在文件存入storage.objects前自动校验用户配额,并原子更新已用存储:

CREATE OR REPLACE FUNCTION check_and_update_storage_quota()
RETURNS TRIGGER AS $$
DECLARE
    user_quota INT;
    user_used INT;
    new_file_size INT;
BEGIN
    -- 锁定用户行,防止并发修改时的竞态
    SELECT storage_quota, storage_used INTO user_quota, user_used
    FROM users
    WHERE user_id = NEW.owner_id
    FOR UPDATE;

    new_file_size = NEW.size;

    -- 校验剩余空间
    IF (user_used + new_file_size) > user_quota THEN
        RAISE EXCEPTION '存储空间不足,剩余配额:% bytes', (user_quota - user_used);
    END IF;

    -- 原子更新已用存储
    UPDATE users
    SET storage_used = user_used + new_file_size
    WHERE user_id = NEW.owner_id;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

2. 创建触发器关联到存储表

让函数在storage.objects插入新文件时自动执行:

CREATE TRIGGER trigger_check_storage_quota
BEFORE INSERT ON storage.objects
FOR EACH ROW
EXECUTE FUNCTION check_and_update_storage_quota();

3. 配置权限

确保Supabase的storage角色有执行函数和访问用户表的权限:

GRANT EXECUTE ON FUNCTION check_and_update_storage_quota() TO storage;
GRANT SELECT, UPDATE ON users TO storage;

二、备选方案:改进API端点(原子化RPC调用)

如果不想用触发器,可通过Supabase RPC函数实现原子化的校验+更新,避免分步操作的竞态问题:

1. 创建RPC函数

CREATE OR REPLACE FUNCTION update_storage_used_if_quota_enough(user_id UUID, add_size INT)
RETURNS JSON AS $$
DECLARE
    user_quota INT;
    user_used INT;
BEGIN
    SELECT storage_quota, storage_used INTO user_quota, user_used
    FROM users
    WHERE user_id = $1
    FOR UPDATE;

    IF (user_used + add_size) > user_quota THEN
        RETURN '{"success": false}'::JSON;
    END IF;

    UPDATE users
    SET storage_used = user_used + add_size
    WHERE user_id = $1;

    RETURN '{"success": true}'::JSON;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

2. SvelteKit端点调用RPC

在/api/upload-params中直接调用该RPC,确保校验和更新在一个事务内完成:

import { supabase } from '$lib/supabase';

export async function POST({ request }) {
    const { userId, fileSize } = await request.json();

    const { data, error } = await supabase.rpc('update_storage_used_if_quota_enough', {
        user_id: userId,
        add_size: fileSize
    });

    if (error || !data.success) {
        return new Response(
            JSON.stringify({ error: error?.message || '存储空间不足' }),
            { status: 400 }
        );
    }

    // 生成Uppy所需的AWS上传参数
    const uploadParams = await generateAWSUploadParams();
    return new Response(JSON.stringify(uploadParams), { status: 200 });
}

关键注意事项

  • 并发处理:两种方案都用了FOR UPDATE锁定用户行,确保同一时间只有一个请求能修改该用户的存储数据,彻底避免竞态。
  • 删除文件的反向更新:可添加AFTER DELETE触发器,在文件删除时自动减去对应大小的已用存储。
  • 分片上传适配:如果用Uppy分片上传,只需关注最终合并后的文件大小(Supabase存储会生成单条storage.objects记录),无需处理分片的中间步骤。
  • 错误提示:前端需捕获上传时的数据库异常,将配额不足的信息反馈给用户。

内容的提问来源于stack exchange,提问作者Christian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 19:52:34