Flutter中监听Supabase内用户位置指定半径内的表插入事件
解决Supabase Realtime监听指定半径内插入操作的问题
问题根源
你当前的写法无效,是因为Supabase Realtime的PostgresChangeFilter不允许在column字段中直接使用SQL空间函数(比如ST_Distance),column参数只能指定表的实际列名,无法解析复杂表达式。
可行解决方案
方案1:使用自定义SQL过滤器(Realtime v2支持)
如果你的Supabase使用Realtime v2,可以通过filter的custom类型直接编写WHERE条件表达式,实现基于空间距离的过滤:
late RealtimeChannel _violationsListener; void subscribeToViolations(LatLng userLocation) { const radius = 10000; // 10公里(米) // 用ST_DistanceSphere计算实际米数距离,避免经纬度度数的误差 final filterSql = ''' ST_DistanceSphere(reporting_location, ST_SetSRID(ST_MakePoint(${userLocation.longitude}, ${userLocation.latitude}), 4326)) < $radius '''; // 频道名带上位置参数,避免用户位置变化时的订阅冲突 _violationsListener = supabase .channel('violations-channel-${userLocation.latitude}-${userLocation.longitude}') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'violations', filter: PostgresChangeFilter.custom(filterSql), callback: (payload) async { final json = payload.newRecord; debugPrint('收到符合条件的插入记录:'); debugPrint(json.toString()); }, ) .subscribe(); }
注意:用户位置更新时,需先调用
_violationsListener.unsubscribe()取消旧订阅,再重新执行上述订阅逻辑。
方案2:结合行级安全(RLS)策略
如果需要更安全的动态过滤逻辑,可以利用RLS策略结合会话变量实现:
- 先启用
violations表的RLS:
ALTER TABLE public.violations ENABLE ROW LEVEL SECURITY;
- 创建RLS策略,基于会话变量过滤半径内的记录:
CREATE POLICY "Allow viewing violations within radius" ON public.violations FOR SELECT USING ( ST_DistanceSphere( reporting_location, ST_SetSRID(ST_MakePoint(current_setting('app.user_longitude')::float8, current_setting('app.user_latitude')::float8), 4326) ) < current_setting('app.user_radius')::int );
- 创建设置会话变量的存储过程:
CREATE OR REPLACE FUNCTION public.set_session_variables(user_latitude float8, user_longitude float8, user_radius int) RETURNS void AS $$ BEGIN PERFORM set_config('app.user_latitude', user_latitude::text, true); PERFORM set_config('app.user_longitude', user_longitude::text, true); PERFORM set_config('app.user_radius', user_radius::text, true); END; $$ LANGUAGE plpgsql SECURITY DEFINER;
- Flutter端订阅逻辑:
late RealtimeChannel _violationsListener; void subscribeToViolations(LatLng userLocation) async { const radius = 10000; // 先设置会话变量,让RLS策略生效 await supabase.rpc('set_session_variables', params: { 'user_latitude': userLocation.latitude, 'user_longitude': userLocation.longitude, 'user_radius': radius, }); // 订阅插入事件,RLS会自动过滤符合条件的记录 _violationsListener = supabase .channel('violations-channel') .onPostgresChanges( event: PostgresChangeEvent.insert, schema: 'public', table: 'violations', callback: (payload) async { final json = payload.newRecord; debugPrint('收到符合条件的插入记录:'); debugPrint(json.toString()); }, ) .subscribe(); }
额外注意事项
- 确保
reporting_location列的类型是geometry(Point, 4326)或geography(Point, 4326),否则空间函数无法正常计算距离。 - 用户位置更新时,同样需要取消旧订阅、重新设置会话变量并创建新订阅。
内容的提问来源于stack exchange,提问作者Tim Kariuki
相关产品推荐
相关产品推荐

