在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,步骤如下:
- 首次打开App时,检查本地有没有存
device_uuid - 没有的话用
uuid包生成一个,存在本地 - 每次启动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)里
这样整理下来,应该完全适配你的场景,有不清楚的地方随时问哈~




