406 lines
11 KiB
PL/PgSQL
406 lines
11 KiB
PL/PgSQL
-- ============================================
|
||
-- 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 $$;
|