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

126 lines
5.8 KiB
PL/PgSQL

-- 学生成就系统表结构
-- 用于记录学生获得的各种成就和徽章
-- 成就定义表
CREATE TABLE IF NOT EXISTS public.ak_achievements (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(128) NOT NULL UNIQUE, -- 成就名称 (添加唯一约束)
description TEXT, -- 成就描述
category VARCHAR(32) DEFAULT 'general', -- 成就分类 (training, skill, progress, persistence, special)
difficulty VARCHAR(16) DEFAULT 'easy', -- 难度等级 (easy, medium, hard, expert)
points INT DEFAULT 10, -- 奖励积分
icon VARCHAR(16) DEFAULT '🏆', -- 成就图标
requirements TEXT, -- 获得条件说明
is_active BOOLEAN DEFAULT true, -- 是否启用
sort_order INT DEFAULT 0, -- 排序顺序
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
COMMENT ON TABLE public.ak_achievements IS '成就定义表';
-- 学生成就记录表
CREATE TABLE IF NOT EXISTS public.ak_student_achievements (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
student_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 学生ID
achievement_id uuid REFERENCES public.ak_achievements(id) ON DELETE CASCADE, -- 成就ID
unlocked_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 获得时间
progress_current INT DEFAULT 0, -- 当前进度
progress_target INT DEFAULT 1, -- 目标进度
is_featured BOOLEAN DEFAULT false, -- 是否为精选成就
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
COMMENT ON TABLE public.ak_student_achievements IS '学生成就记录表';
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_achievements_category ON public.ak_achievements(category);
CREATE INDEX IF NOT EXISTS idx_achievements_difficulty ON public.ak_achievements(difficulty);
CREATE INDEX IF NOT EXISTS idx_student_achievements_student_id ON public.ak_student_achievements(student_id);
CREATE INDEX IF NOT EXISTS idx_student_achievements_achievement_id ON public.ak_student_achievements(achievement_id);
CREATE INDEX IF NOT EXISTS idx_student_achievements_unlocked_at ON public.ak_student_achievements(unlocked_at);
-- 防止重复获得同一成就
ALTER TABLE public.ak_student_achievements
ADD CONSTRAINT uk_student_achievement_unique UNIQUE (student_id, achievement_id);
-- 插入一些基础成就
INSERT INTO public.ak_achievements (name, description, category, difficulty, points, icon, requirements) VALUES
('初出茅庐', '完成第一次训练记录', 'training', 'easy', 10, '🎯', '完成任意一次训练记录'),
('坚持不懈', '连续训练7天', 'persistence', 'medium', 50, '🔥', '连续7天都有训练记录'),
('技术精进', '单项技能评分达到90分以上', 'skill', 'hard', 100, '', '在任意技能项目中获得90分以上评分'),
('全能选手', '完成所有训练项目', 'training', 'expert', 200, '🏆', '完成系统中所有的训练项目'),
('进步之星', '单月内平均分提升10分以上', 'progress', 'medium', 75, '📈', '在一个月内平均成绩提升10分或以上'),
('月度之王', '成为月度训练积分第一名', 'special', 'expert', 300, '👑', '在月度排行榜中获得第一名')
ON CONFLICT (name) DO NOTHING;
-- 创建触发器函数,自动检查成就获得条件
CREATE OR REPLACE FUNCTION check_student_achievements()
RETURNS TRIGGER AS $$
DECLARE
student_uuid uuid;
achievement_uuid uuid;
assignment_count INT;
avg_score FLOAT;
consecutive_days INT;
BEGIN
-- 获取学生ID
student_uuid := NEW.student_id;
-- 检查"初出茅庐"成就 - 完成第一次训练记录
SELECT id INTO achievement_uuid FROM public.ak_achievements WHERE name = '初出茅庐';
IF achievement_uuid IS NOT NULL THEN
SELECT COUNT(*) INTO assignment_count
FROM public.ak_assignment_submissions
WHERE student_id = student_uuid AND status = 'submitted';
IF assignment_count = 1 THEN
INSERT INTO public.ak_student_achievements (student_id, achievement_id)
VALUES (student_uuid, achievement_uuid)
ON CONFLICT (student_id, achievement_id) DO NOTHING;
END IF;
END IF;
-- 检查"技术精进"成就 - 90分以上评分
SELECT id INTO achievement_uuid FROM public.ak_achievements WHERE name = '技术精进';
IF achievement_uuid IS NOT NULL AND NEW.final_score >= 90 THEN
INSERT INTO public.ak_student_achievements (student_id, achievement_id)
VALUES (student_uuid, achievement_uuid)
ON CONFLICT (student_id, achievement_id) DO NOTHING;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器
DROP TRIGGER IF EXISTS trigger_check_achievements ON public.ak_assignment_submissions;
CREATE TRIGGER trigger_check_achievements
AFTER INSERT OR UPDATE ON public.ak_assignment_submissions
FOR EACH ROW
EXECUTE FUNCTION check_student_achievements();
-- 创建获取用户成就统计的函数
CREATE OR REPLACE FUNCTION get_student_achievement_stats(p_student_id uuid)
RETURNS TABLE(
total_achievements INT,
total_points INT,
completion_rate FLOAT,
recent_achievements_count INT
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(sa.id)::INT as total_achievements,
COALESCE(SUM(a.points), 0)::INT as total_points,
CASE
WHEN COUNT(DISTINCT a_all.id) > 0
THEN (COUNT(sa.id)::FLOAT / COUNT(DISTINCT a_all.id) * 100)
ELSE 0.0
END as completion_rate,
COUNT(CASE WHEN sa.unlocked_at >= NOW() - INTERVAL '7 days' THEN 1 END)::INT as recent_achievements_count
FROM public.ak_achievements a_all
LEFT JOIN public.ak_student_achievements sa ON a_all.id = sa.achievement_id AND sa.student_id = p_student_id
LEFT JOIN public.ak_achievements a ON sa.achievement_id = a.id
WHERE a_all.is_active = true;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;