Files
akmon/fix_triggers.sql
2026-01-20 08:04:15 +08:00

230 lines
7.2 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- =====================================================
-- 修复触发器冲突脚本
-- 解决 "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;