-- ============================================ -- 07_custom_report_rpcs.sql -- 自定义报表创建与管理 RPC 定义 -- ============================================ -- 目标: -- 1) 为 `pages/mall/analytics/custom-report.uvue` 提供安全的数据服务 -- 2) 确保用户记录存在,解决外键约束问题 -- 3) 创建自定义报表并生成初始数据(metrics + rows) -- -- 依赖前置脚本: -- - 01_create_tables.sql(users 表) -- - ANALYTICS_DB_SCHEMA.sql(analytics_reports / analytics_report_metrics / analytics_report_rows) -- -- 使用说明: -- - 前端通过 supabase-js / UTS 调用 `rpc()` 访问本文件中的函数 -- - 所有函数仅对 `authenticated` 角色开放执行权限 -- ============================================ -- -------------------------------------------- -- 1. 确保用户记录存在(Upsert User) -- -------------------------------------------- -- 说明: -- - 如果 users 表中不存在当前用户记录,则插入 -- - 如果已存在,则更新最后登录时间等信息 -- - 解决 analytics_reports.owner_user_id 外键约束问题 CREATE OR REPLACE FUNCTION public.rpc_ensure_user_record( p_user_id uuid, p_email text DEFAULT NULL, p_phone text DEFAULT NULL, p_nickname text DEFAULT NULL ) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_user_id uuid; BEGIN -- 检查用户是否存在 SELECT id INTO v_user_id FROM public.users WHERE id = p_user_id; IF v_user_id IS NULL THEN -- 用户不存在,插入新记录 INSERT INTO public.users ( id, email, phone, nickname, last_login_at, created_at, updated_at ) VALUES ( p_user_id, p_email, p_phone, COALESCE(p_nickname, COALESCE(split_part(p_email, '@', 1), '用户')), NOW(), NOW(), NOW() ) RETURNING id INTO v_user_id; ELSE -- 用户已存在,更新信息 UPDATE public.users SET email = COALESCE(p_email, email), phone = COALESCE(p_phone, phone), nickname = COALESCE(p_nickname, nickname), last_login_at = NOW(), updated_at = NOW() WHERE id = p_user_id; v_user_id := p_user_id; END IF; RETURN v_user_id; END; $$; REVOKE ALL ON FUNCTION public.rpc_ensure_user_record(uuid,text,text,text) FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.rpc_ensure_user_record(uuid,text,text,text) TO authenticated; -- -------------------------------------------- -- 2. 创建自定义报表(含初始数据生成) -- -------------------------------------------- -- 说明: -- - 创建 analytics_reports 记录 -- - 根据 period 和选中的指标,生成 analytics_report_metrics -- - 根据 period 聚合 orders 数据,生成 analytics_report_rows CREATE OR REPLACE FUNCTION public.rpc_create_custom_report( p_title text, p_description text DEFAULT '', p_period text DEFAULT '30d', -- 7d/30d/90d/1y p_metrics text[] DEFAULT ARRAY['gmv', 'orders', 'users'], -- 选中的指标列表 p_chart_type text DEFAULT 'line' -- 图表类型(暂不存储,仅用于后续扩展) ) RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_user_id uuid; v_report_id uuid; v_start_date date; v_end_date date; v_metric_key text; v_metric_label text; v_metric_value numeric; v_total_gmv numeric := 0; v_total_orders integer := 0; v_total_users integer := 0; v_avg_order_amount numeric := 0; BEGIN -- 1. 获取当前用户 ID v_user_id := auth.uid(); IF v_user_id IS NULL THEN RAISE EXCEPTION '用户未登录'; END IF; -- 2. 确保用户记录存在 PERFORM public.rpc_ensure_user_record( v_user_id, NULL, -- email 从 auth.users 获取,这里不传 NULL, -- phone 从 auth.users 获取,这里不传 NULL -- nickname 从 auth.users 获取,这里不传 ); -- 3. 计算时间范围 v_end_date := CURRENT_DATE; CASE p_period WHEN '7d' THEN v_start_date := v_end_date - INTERVAL '7 days'; WHEN '30d' THEN v_start_date := v_end_date - INTERVAL '30 days'; WHEN '90d' THEN v_start_date := v_end_date - INTERVAL '90 days'; WHEN '1y' THEN v_start_date := v_end_date - INTERVAL '1 year'; ELSE v_start_date := v_end_date - INTERVAL '30 days'; -- 默认 30 天 END CASE; -- 4. 创建报表记录 INSERT INTO public.analytics_reports ( owner_user_id, title, description, type, period, date_start, date_end, status, generated_at, created_at, updated_at ) VALUES ( v_user_id, p_title, p_description, 'custom', p_period, v_start_date, v_end_date, 'ready', NOW(), NOW(), NOW() ) RETURNING id INTO v_report_id; -- 5. 聚合订单数据,计算总指标 SELECT COALESCE(SUM(o.total_amount), 0), COUNT(DISTINCT o.id), COUNT(DISTINCT o.user_id), CASE WHEN COUNT(DISTINCT o.id) > 0 THEN COALESCE(SUM(o.total_amount), 0) / COUNT(DISTINCT o.id) ELSE 0 END INTO v_total_gmv, v_total_orders, v_total_users, v_avg_order_amount FROM public.orders o WHERE o.created_at >= v_start_date AND o.created_at <= v_end_date AND o.status = 2; -- 已完成订单 -- 6. 生成核心指标(analytics_report_metrics) -- GMV IF 'gmv' = ANY(p_metrics) THEN INSERT INTO public.analytics_report_metrics ( report_id, metric_key, metric_label, metric_value_num, format, icon, color ) VALUES ( v_report_id, 'gmv_total', '总GMV', v_total_gmv, 'currency', '💰', '#4caf50' ); END IF; -- 订单数 IF 'orders' = ANY(p_metrics) THEN INSERT INTO public.analytics_report_metrics ( report_id, metric_key, metric_label, metric_value_num, format, icon, color ) VALUES ( v_report_id, 'orders_total', '总订单数', v_total_orders, 'number', '📦', '#2196f3' ); END IF; -- 用户数 IF 'users' = ANY(p_metrics) THEN INSERT INTO public.analytics_report_metrics ( report_id, metric_key, metric_label, metric_value_num, format, icon, color ) VALUES ( v_report_id, 'users_total', '下单用户数', v_total_users, 'number', '👥', '#ff9800' ); END IF; -- 客单价 IF 'avg_order' = ANY(p_metrics) THEN INSERT INTO public.analytics_report_metrics ( report_id, metric_key, metric_label, metric_value_num, format, icon, color ) VALUES ( v_report_id, 'avg_order_amount', '客单价', v_avg_order_amount, 'currency', '💵', '#9c27b0' ); END IF; -- 7. 生成明细行(analytics_report_rows)- 按天聚合 INSERT INTO public.analytics_report_rows ( report_id, row_date, gmv, orders, users, avg_order_amount ) SELECT v_report_id, o.created_at::date AS row_date, COALESCE(SUM(o.total_amount), 0) AS gmv, COUNT(DISTINCT o.id) AS orders, COUNT(DISTINCT o.user_id) AS users, CASE WHEN COUNT(DISTINCT o.id) > 0 THEN COALESCE(SUM(o.total_amount), 0) / COUNT(DISTINCT o.id) ELSE 0 END AS avg_order_amount FROM public.orders o WHERE o.created_at >= v_start_date AND o.created_at <= v_end_date AND o.status = 2 GROUP BY o.created_at::date ORDER BY o.created_at::date; RETURN v_report_id; END; $$; REVOKE ALL ON FUNCTION public.rpc_create_custom_report(text,text,text,text[],text) FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.rpc_create_custom_report(text,text,text,text[],text) TO authenticated; -- -------------------------------------------- -- 3. 更新自定义报表(仅更新基本信息) -- -------------------------------------------- -- 说明: -- - 更新报表的标题、描述、周期 -- - 不重新生成数据(如需重新生成,删除后重建) CREATE OR REPLACE FUNCTION public.rpc_update_custom_report( p_report_id uuid, p_title text, p_description text DEFAULT NULL, p_period text DEFAULT NULL ) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_user_id uuid; BEGIN -- 1. 获取当前用户 ID v_user_id := auth.uid(); IF v_user_id IS NULL THEN RAISE EXCEPTION '用户未登录'; END IF; -- 2. 更新报表(仅限所有者) UPDATE public.analytics_reports SET title = p_title, description = COALESCE(p_description, description), period = COALESCE(p_period, period), updated_at = NOW() WHERE id = p_report_id AND owner_user_id = v_user_id; -- FOUND 是 PostgreSQL 的特殊变量,UPDATE 后自动设置 IF NOT FOUND THEN RAISE EXCEPTION '报表不存在或无权限修改'; END IF; RETURN true; END; $$; REVOKE ALL ON FUNCTION public.rpc_update_custom_report(uuid,text,text,text) FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.rpc_update_custom_report(uuid,text,text,text) TO authenticated; -- -------------------------------------------- -- 4. 删除自定义报表(级联删除相关数据) -- -------------------------------------------- -- 说明: -- - 删除报表记录(CASCADE 会自动删除 metrics 和 rows) -- - 仅允许所有者删除 CREATE OR REPLACE FUNCTION public.rpc_delete_custom_report( p_report_id uuid ) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_user_id uuid; BEGIN -- 1. 获取当前用户 ID v_user_id := auth.uid(); IF v_user_id IS NULL THEN RAISE EXCEPTION '用户未登录'; END IF; -- 2. 删除报表(仅限所有者,CASCADE 会自动删除 metrics 和 rows) DELETE FROM public.analytics_reports WHERE id = p_report_id AND owner_user_id = v_user_id AND type = 'custom'; -- FOUND 是 PostgreSQL 的特殊变量,DELETE 后自动设置 IF NOT FOUND THEN RAISE EXCEPTION '报表不存在或无权限删除'; END IF; RETURN true; END; $$; REVOKE ALL ON FUNCTION public.rpc_delete_custom_report(uuid) FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.rpc_delete_custom_report(uuid) TO authenticated; -- ============================================ -- 完成提示 -- ============================================ DO $$ BEGIN RAISE NOTICE 'Custom report RPCs created successfully.'; RAISE NOTICE 'Functions:'; RAISE NOTICE ' - rpc_ensure_user_record(uuid, text, text, text)'; RAISE NOTICE ' - rpc_create_custom_report(text, text, text, text[], text)'; RAISE NOTICE ' - rpc_update_custom_report(uuid, text, text, text)'; RAISE NOTICE ' - rpc_delete_custom_report(uuid)'; END $$;