You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在Supabase中使用UUID追踪未认证免费用户的最佳实践咨询

在Supabase中使用UUID追踪未认证免费用户的最佳实践咨询

嘿,看你的场景是用Flutter开发App、Supabase做后端,现在只有付费的Google登录用户在users表,免费用户完全没数据追踪对吧?想用user_devices表存设备UUID来解决这个问题,我给你梳理几个实操性强的最佳实践,都是实际项目里踩坑后总结的:

一、先搞定user_devices表的合理结构

这个表是核心,既要能唯一标识免费用户,还要能和后续升级的付费用户打通数据,推荐的字段设计如下:

CREATE TABLE user_devices (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  device_uuid UUID NOT NULL UNIQUE, -- 核心:唯一标识设备/免费用户
  device_info JSONB, -- 可选:存设备型号、系统版本等辅助信息,方便排查问题
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), -- 首次追踪时间
  last_active_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), -- 最后活跃时间,用来清理僵尸数据
  premium_user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL -- 关联付费用户id,升级时填充
);

-- 给常用查询建索引,避免数据多了之后变慢
CREATE INDEX idx_user_devices_device_uuid ON user_devices(device_uuid);
CREATE INDEX idx_user_devices_premium_user_id ON user_devices(premium_user_id);

二、Flutter端生成和维护设备UUID

别用硬件UUID(隐私合规风险高,跨平台也不兼容),直接生成一个随机UUID存在本地存储里,比如用shared_preferences,步骤如下:

  1. 首次打开App时,检查本地有没有存device_uuid
  2. 没有的话用uuid包生成一个,存在本地
  3. 每次启动App时,把这个UUID同步到Supabase,并且更新最后活跃时间

Flutter代码示例:

import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

Future<String> getOrCreateDeviceUuid() async {
  final prefs = await SharedPreferences.getInstance();
  String? deviceUuid = prefs.getString('device_uuid');

  // 首次启动,生成并存储UUID
  if (deviceUuid == null) {
    deviceUuid = const Uuid().v4();
    await prefs.setString('device_uuid', deviceUuid);
    // 插入新的设备记录到Supabase
    await Supabase.instance.client
        .from('user_devices')
        .upsert({
          'device_uuid': deviceUuid,
          'device_info': {
            // 可选:用device_info_plus包获取设备信息
            'platform': 'android',
            'model': 'Pixel 7',
            'os_version': '14'
          }
        });
  }

  // 每次启动更新最后活跃时间
  await Supabase.instance.client
      .from('user_devices')
      .update({'last_active_at': DateTime.now().toIso8601String()})
      .eq('device_uuid', deviceUuid);

  return deviceUuid;
}

三、Supabase RLS规则一定要配置好!

这步最容易踩坑,必须确保匿名用户(免费用户)只能操作自己的设备记录,不能篡改别人的。给你几个核心规则:

-- 先启用RLS,这是Supabase数据安全的基础
ALTER TABLE user_devices ENABLE ROW LEVEL SECURITY;

-- 允许匿名用户插入新的设备记录(首次初始化时用)
CREATE POLICY "Anon users can insert their device record" ON user_devices
  FOR INSERT WITH CHECK (auth.role() = 'anon');

-- 允许匿名用户更新自己设备的最后活跃时间
CREATE POLICY "Anon users can update their device's last active time" ON user_devices
  FOR UPDATE USING (auth.role() = 'anon');

-- 付费用户可以把自己的设备记录和账号关联(升级时用)
CREATE POLICY "Premium users can link their device to account" ON user_devices
  FOR UPDATE USING (auth.role() = 'authenticated' AND auth.uid() = premium_user_id);

-- 允许匿名用户查看自己的设备记录(如果有需要的话)
CREATE POLICY "Anon users can view their own device record" ON user_devices
  FOR SELECT USING (auth.role() = 'anon');

注:表结构里已经给device_uuid加了UNIQUE约束,数据库会自动拦截重复插入,不用担心匿名用户乱插数据。

四、免费用户升级到付费用户的数据打通

当免费用户通过Google登录变成付费用户时,一定要把之前的设备记录和新的付费账号关联起来,这样就能把免费阶段的行为数据和付费用户打通。

Flutter端代码示例(放在登录成功的回调里):

// 监听登录状态变化,用户登录成功后关联设备
Supabase.instance.client.auth.onAuthStateChange.listen((data) {
  final event = data.event;
  final session = data.session;

  if (event == AuthChangeEvent.signedIn && session != null) {
    // 获取本地存储的device_uuid
    getOrCreateDeviceUuid().then((deviceUuid) async {
      // 把设备记录关联到当前付费用户
      await Supabase.instance.client
          .from('user_devices')
          .update({'premium_user_id': session.user.id})
          .eq('device_uuid', deviceUuid);
    });
  }
});

五、几个容易忽略的细节

  • 隐私合规:如果你的App面向欧盟、加州等地区,必须告知用户你在收集设备UUID,还要提供删除数据的选项(比如加一个“清除我的数据”按钮,调用Supabase API删除对应的user_devices记录)
  • 清理僵尸数据:定期删除last_active_at超过30天的设备记录,避免表越来越大。可以用Supabase的pg_cron扩展做定时任务,比如每周清理一次
  • 测试RLS规则:一定要在Supabase控制台的SQL编辑器里测试规则,比如用set role anon;模拟匿名用户,看看能不能篡改别人的设备记录,确保数据安全
  • UUID存储可靠性:如果用SharedPreferences存UUID,注意有些设备可能会清掉App缓存,导致UUID丢失。如果对数据连续性要求高,可以考虑用Flutter的hive包做本地存储,或者把UUID存在Keychain(iOS)/Keystore(Android)里

这样整理下来,应该完全适配你的场景,有不清楚的地方随时问哈~

火山引擎 最新活动