-- ===================================================== -- 修复触发器冲突脚本 -- 解决 "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;