230 lines
7.2 KiB
PL/PgSQL
230 lines
7.2 KiB
PL/PgSQL
-- =====================================================
|
||
-- 修复触发器冲突脚本
|
||
-- 解决 "trigger already exists" 错误
|
||
-- =====================================================
|
||
|
||
-- 清理所有可能存在的触发器
|
||
DO $$
|
||
BEGIN
|
||
-- 删除用户角色相关触发器
|
||
DROP TRIGGER IF EXISTS trigger_update_user_roles_updated_at ON public.user_roles;
|
||
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
||
|
||
-- 删除可能存在的旧版本触发器
|
||
DROP TRIGGER IF EXISTS update_user_roles_updated_at ON public.user_roles;
|
||
DROP TRIGGER IF EXISTS handle_new_user_trigger ON auth.users;
|
||
|
||
RAISE NOTICE '✅ 已清理所有现有触发器';
|
||
END $$;
|
||
|
||
-- 清理可能存在的函数冲突
|
||
DO $$
|
||
BEGIN
|
||
-- 删除函数(CASCADE 会自动删除依赖的触发器)
|
||
DROP FUNCTION IF EXISTS public.update_user_roles_updated_at() CASCADE;
|
||
DROP FUNCTION IF EXISTS public.handle_new_user() CASCADE;
|
||
DROP FUNCTION IF EXISTS public.update_user_role(UUID, TEXT, UUID) CASCADE;
|
||
DROP FUNCTION IF EXISTS public.get_user_role(UUID) CASCADE;
|
||
DROP FUNCTION IF EXISTS public.batch_update_user_roles(JSONB, UUID) CASCADE;
|
||
DROP FUNCTION IF EXISTS public.sync_user_role_metadata(UUID) CASCADE;
|
||
|
||
RAISE NOTICE '✅ 已清理可能冲突的函数';
|
||
END $$;
|
||
|
||
-- 重新创建核心函数
|
||
-- 1. 更新时间戳函数
|
||
CREATE OR REPLACE FUNCTION public.update_user_roles_updated_at()
|
||
RETURNS TRIGGER AS $$
|
||
BEGIN
|
||
NEW.updated_at = NOW();
|
||
RETURN NEW;
|
||
END;
|
||
$$ LANGUAGE plpgsql;
|
||
|
||
-- 2. 同步用户角色到元数据的函数
|
||
CREATE OR REPLACE FUNCTION public.sync_user_role_metadata(target_user_id UUID)
|
||
RETURNS BOOLEAN AS $$
|
||
DECLARE
|
||
user_role_record RECORD;
|
||
BEGIN
|
||
-- 获取用户角色信息
|
||
SELECT role, class_id, school_id, department, permissions, is_active
|
||
INTO user_role_record
|
||
FROM public.user_roles
|
||
WHERE user_id = target_user_id AND is_active = true;
|
||
|
||
IF FOUND THEN
|
||
-- 更新 auth.users 的元数据
|
||
UPDATE auth.users
|
||
SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) ||
|
||
jsonb_build_object(
|
||
'user_role', user_role_record.role,
|
||
'class_id', user_role_record.class_id,
|
||
'school_id', user_role_record.school_id,
|
||
'department', user_role_record.department,
|
||
'permissions', user_role_record.permissions,
|
||
'role_synced_at', extract(epoch from now())
|
||
)
|
||
WHERE id = target_user_id;
|
||
|
||
RETURN true;
|
||
ELSE
|
||
-- 用户没有活跃角色,清除角色信息
|
||
UPDATE auth.users
|
||
SET raw_user_meta_data = COALESCE(raw_user_meta_data, '{}'::jsonb) ||
|
||
jsonb_build_object(
|
||
'user_role', 'inactive',
|
||
'role_synced_at', extract(epoch from now())
|
||
)
|
||
WHERE id = target_user_id;
|
||
|
||
RETURN false;
|
||
END IF;
|
||
END;
|
||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||
|
||
-- 3. 自动为新用户分配角色的函数
|
||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||
RETURNS TRIGGER AS $$
|
||
DECLARE
|
||
user_role TEXT := 'student'; -- 默认角色
|
||
user_email TEXT := NEW.email;
|
||
user_domain TEXT;
|
||
BEGIN
|
||
-- 提取邮箱域名
|
||
user_domain := split_part(user_email, '@', 2);
|
||
|
||
-- 根据邮箱域名或特定规则分配角色
|
||
CASE
|
||
WHEN user_domain IN ('teacher.edu', 'faculty.edu', 'staff.edu') THEN
|
||
user_role := 'teacher';
|
||
WHEN user_domain IN ('admin.edu', 'management.edu') THEN
|
||
user_role := 'admin';
|
||
WHEN user_email LIKE '%admin%' OR user_email LIKE '%manager%' THEN
|
||
user_role := 'admin';
|
||
WHEN user_email LIKE '%teacher%' OR user_email LIKE '%faculty%' THEN
|
||
user_role := 'teacher';
|
||
ELSE
|
||
user_role := 'student';
|
||
END CASE;
|
||
|
||
-- 插入角色记录
|
||
INSERT INTO public.user_roles (user_id, role, created_by)
|
||
VALUES (NEW.id, user_role, NEW.id);
|
||
|
||
-- 同步到元数据
|
||
PERFORM public.sync_user_role_metadata(NEW.id);
|
||
|
||
RAISE NOTICE '✅ 为用户 % 分配角色: %', NEW.email, user_role;
|
||
|
||
RETURN NEW;
|
||
EXCEPTION WHEN OTHERS THEN
|
||
RAISE WARNING '⚠️ 为用户 % 分配角色失败: %', NEW.email, SQLERRM;
|
||
RETURN NEW;
|
||
END;
|
||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||
|
||
-- 4. 获取用户角色的函数(支持缓存)
|
||
CREATE OR REPLACE FUNCTION public.get_user_role(target_user_id UUID DEFAULT auth.uid())
|
||
RETURNS TEXT AS $$
|
||
DECLARE
|
||
user_role TEXT;
|
||
cached_role TEXT;
|
||
role_synced_at NUMERIC;
|
||
BEGIN
|
||
-- 如果没有用户ID,返回匿名
|
||
IF target_user_id IS NULL THEN
|
||
RETURN 'anonymous';
|
||
END IF;
|
||
|
||
-- 尝试从 auth.users 元数据获取缓存角色
|
||
SELECT
|
||
raw_user_meta_data->>'user_role',
|
||
(raw_user_meta_data->>'role_synced_at')::numeric
|
||
INTO cached_role, role_synced_at
|
||
FROM auth.users
|
||
WHERE id = target_user_id;
|
||
|
||
-- 如果缓存新鲜(5分钟内),直接返回
|
||
IF cached_role IS NOT NULL AND role_synced_at IS NOT NULL
|
||
AND (extract(epoch from now()) - role_synced_at) < 300 THEN
|
||
RETURN cached_role;
|
||
END IF;
|
||
|
||
-- 从角色表获取最新角色
|
||
SELECT role INTO user_role
|
||
FROM public.user_roles
|
||
WHERE user_id = target_user_id AND is_active = true;
|
||
|
||
-- 如果找到角色,同步到元数据
|
||
IF FOUND THEN
|
||
PERFORM public.sync_user_role_metadata(target_user_id);
|
||
RETURN user_role;
|
||
ELSE
|
||
RETURN 'student'; -- 默认角色
|
||
END IF;
|
||
END;
|
||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||
|
||
-- 重新创建触发器
|
||
DO $$
|
||
BEGIN
|
||
-- 创建更新时间戳触发器
|
||
CREATE TRIGGER trigger_update_user_roles_updated_at
|
||
BEFORE UPDATE ON public.user_roles
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION public.update_user_roles_updated_at();
|
||
|
||
-- 创建新用户触发器
|
||
CREATE TRIGGER on_auth_user_created
|
||
AFTER INSERT ON auth.users
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION public.handle_new_user();
|
||
|
||
RAISE NOTICE '✅ 触发器重建完成';
|
||
END $$;
|
||
|
||
-- 验证触发器是否正确创建
|
||
DO $$
|
||
DECLARE
|
||
trigger_count INTEGER;
|
||
BEGIN
|
||
SELECT COUNT(*) INTO trigger_count
|
||
FROM information_schema.triggers
|
||
WHERE trigger_schema = 'public'
|
||
AND trigger_name IN ('trigger_update_user_roles_updated_at', 'on_auth_user_created');
|
||
|
||
IF trigger_count >= 2 THEN
|
||
RAISE NOTICE '🎉 触发器验证通过: % 个触发器已创建', trigger_count;
|
||
ELSE
|
||
RAISE WARNING '⚠️ 触发器验证失败: 只找到 % 个触发器', trigger_count;
|
||
END IF;
|
||
END $$;
|
||
|
||
-- 验证函数是否正确创建
|
||
DO $$
|
||
DECLARE
|
||
function_count INTEGER;
|
||
BEGIN
|
||
SELECT COUNT(*) INTO function_count
|
||
FROM information_schema.routines
|
||
WHERE routine_schema = 'public'
|
||
AND routine_name IN (
|
||
'update_user_roles_updated_at',
|
||
'handle_new_user',
|
||
'sync_user_role_metadata',
|
||
'get_user_role'
|
||
);
|
||
|
||
IF function_count >= 4 THEN
|
||
RAISE NOTICE '🎉 函数验证通过: % 个函数已创建', function_count;
|
||
ELSE
|
||
RAISE WARNING '⚠️ 函数验证失败: 只找到 % 个函数', function_count;
|
||
END IF;
|
||
END $$;
|
||
|
||
-- 完成消息
|
||
SELECT
|
||
'🔧 触发器修复完成' as status,
|
||
'现在可以安全地执行其他SQL脚本' as message;
|