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

977 lines
41 KiB
PL/PgSQL
Raw Permalink 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.
-- 运动训练监测与AI评估平台表结构设计Supabase/PostgreSQL规范含中文注释、索引、外键约束
-- 支持的语言表
CREATE TABLE public.ak_languages (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 语言ID
code VARCHAR(10) NOT NULL UNIQUE, -- 语言代码 (如 en-US, zh-CN)
name VARCHAR(50) NOT NULL, -- 语言名称
native_name VARCHAR(50) NOT NULL, -- 本地语言名称
is_active BOOLEAN DEFAULT true, -- 是否启用
is_default BOOLEAN DEFAULT false, -- 是否默认语言
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_languages IS '支持的语言表';
-- 地区表
CREATE TABLE public.ak_regions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 地区ID
name VARCHAR(128) NOT NULL, -- 地区名称
parent_id uuid REFERENCES public.ak_regions(id), -- 上级地区ID
level INT, -- 地区级别 1省 2市 3区县 4街道
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_regions IS '地区表';
COMMENT ON COLUMN public.ak_regions.name IS '地区名称';
COMMENT ON COLUMN public.ak_regions.parent_id IS '上级地区ID';
COMMENT ON COLUMN public.ak_regions.level IS '地区级别';
-- 学校表
CREATE TABLE public.ak_schools (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 学校ID
name VARCHAR(128) NOT NULL, -- 学校名称
region_id uuid REFERENCES public.ak_regions(id), -- 所属地区
type VARCHAR(32), -- 学校类型
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_schools IS '学校表';
COMMENT ON COLUMN public.ak_schools.region_id IS '所属地区';
-- 年级表
CREATE TABLE public.ak_grades (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 年级ID
school_id uuid REFERENCES public.ak_schools(id) ON DELETE CASCADE, -- 所属学校
name VARCHAR(32) NOT NULL, -- 年级名称
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_grades IS '年级表';
COMMENT ON COLUMN public.ak_grades.school_id IS '所属学校';
-- 班级表
CREATE TABLE public.ak_classes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 班级ID
grade_id uuid REFERENCES public.ak_grades(id) ON DELETE CASCADE, -- 所属年级
name VARCHAR(32) NOT NULL, -- 班级名称
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_classes IS '班级表';
COMMENT ON COLUMN public.ak_classes.grade_id IS '所属年级';
-- 用户表
CREATE TABLE public.ak_users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 用户ID
username VARCHAR(64) UNIQUE NOT NULL, -- 用户名
email VARCHAR(128) UNIQUE NOT NULL, -- 邮箱
password_hash VARCHAR(256) NOT NULL, -- 密码哈希
gender VARCHAR(16) DEFAULT 'other', -- 性别
birthday DATE, -- 生日
height_cm INT, -- 身高
weight_kg INT, -- 体重
bio TEXT, -- biography
phone VARCHAR(32), -- 手机号码
avatar_url TEXT, -- 头像
region_id uuid REFERENCES public.ak_regions(id), -- 所属地区
school_id uuid REFERENCES public.ak_schools(id), -- 所属学校
grade_id uuid REFERENCES public.ak_grades(id), -- 所属年级
class_id uuid REFERENCES public.ak_classes(id), -- 所属班级
role VARCHAR(32) DEFAULT 'student', -- 角色
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 更新时间
auth_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, -- Supabase Auth ID
preferred_language uuid REFERENCES public.ak_languages(id) -- 用户偏好语言
);
COMMENT ON TABLE public.ak_users IS '用户表';
COMMENT ON COLUMN public.ak_users.username IS '用户名';
COMMENT ON COLUMN public.ak_users.email IS '邮箱';
COMMENT ON COLUMN public.ak_users.gender IS '性别';
COMMENT ON COLUMN public.ak_users.region_id IS '所属地区';
COMMENT ON COLUMN public.ak_users.school_id IS '所属学校';
COMMENT ON COLUMN public.ak_users.grade_id IS '所属年级';
COMMENT ON COLUMN public.ak_users.class_id IS '所属班级';
COMMENT ON COLUMN public.ak_users.role IS '用户角色';
COMMENT ON COLUMN public.ak_users.preferred_language IS '用户偏好语言';
CREATE INDEX idx_users_region_id ON public.ak_users(region_id);
CREATE INDEX idx_users_school_id ON public.ak_users(school_id);
CREATE INDEX idx_users_grade_id ON public.ak_users(grade_id);
CREATE INDEX idx_users_class_id ON public.ak_users(class_id);
-- 用户联系方式表
CREATE TABLE public.ak_user_contacts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 联系方式ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
contact_type VARCHAR(32) NOT NULL, -- 联系方式类型
contact_value VARCHAR(128) NOT NULL, -- 联系方式内容
is_primary BOOLEAN DEFAULT false, -- 是否主联系方式
remark VARCHAR(128), -- 备注
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_user_contacts IS '用户联系方式表';
COMMENT ON COLUMN public.ak_user_contacts.contact_type IS '联系方式类型';
COMMENT ON COLUMN public.ak_user_contacts.contact_value IS '联系方式内容';
CREATE INDEX idx_user_contacts_user_id ON public.ak_user_contacts(user_id);
-- 用户个人展示设置表
CREATE TABLE public.ak_user_profiles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 个人展示ID
user_id uuid UNIQUE REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
avatar_url TEXT, -- 头像S3地址
background_url TEXT, -- 背景图/视频S3地址
declaration TEXT, -- 个人宣言
status_text TEXT, -- 近况
status_media_url TEXT, -- 近况配图/视频S3地址
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_user_profiles IS '用户个人展示设置表';
-- 用户-班级多对多关系
CREATE TABLE public.ak_user_classes (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 关系ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
class_id uuid REFERENCES public.ak_classes(id) ON DELETE CASCADE, -- 班级ID
role VARCHAR(32), -- 角色
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_user_classes IS '用户-班级多对多关系表';
CREATE INDEX idx_user_classes_user_id ON public.ak_user_classes(user_id);
CREATE INDEX idx_user_classes_class_id ON public.ak_user_classes(class_id);
-- 设备表
CREATE TABLE public.ak_devices (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 设备ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
device_type VARCHAR(32) NOT NULL, -- 设备类型
device_name VARCHAR(64), -- 设备名称
device_mac VARCHAR(64), -- 设备MAC
bind_time TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 绑定时间
status VARCHAR(16) DEFAULT 'active', -- 状态
extra JSONB -- 扩展信息
);
COMMENT ON TABLE public.ak_devices IS '设备表';
CREATE INDEX idx_devices_user_id ON public.ak_devices(user_id);
-- 训练计划表
CREATE TABLE public.ak_training_plans (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 计划ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
plan_name VARCHAR(128) NOT NULL, -- 计划名称
description TEXT, -- 描述
start_date DATE, -- 开始日期
end_date DATE, -- 结束日期
status VARCHAR(16) DEFAULT 'active', -- 状态
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_training_plans IS '训练计划表';
CREATE INDEX idx_training_plans_user_id ON public.ak_training_plans(user_id);
-- 训练计划明细表
CREATE TABLE public.ak_training_plan_items (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 明细ID
plan_id uuid REFERENCES public.ak_training_plans(id) ON DELETE CASCADE, -- 计划ID
item_date DATE NOT NULL, -- 明细日期
activity_type VARCHAR(64), -- 活动类型
target_value FLOAT, -- 目标数值
unit VARCHAR(16), -- 单位
description TEXT, -- 描述
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_training_plan_items IS '训练计划明细表';
CREATE INDEX idx_training_plan_items_plan_id ON public.ak_training_plan_items(plan_id);
-- 训练记录表
CREATE TABLE public.ak_training_records (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 记录ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
plan_item_id uuid REFERENCES public.ak_training_plan_items(id), -- 计划明细ID
device_id uuid REFERENCES public.ak_devices(id), -- 设备ID
activity_type VARCHAR(64), -- 活动类型
start_time TIMESTAMP WITH TIME ZONE, -- 开始时间
end_time TIMESTAMP WITH TIME ZONE, -- 结束时间
duration_sec INT, -- 时长
distance_km FLOAT, -- 距离
calories FLOAT, -- 卡路里
steps INT, -- 步数
avg_heart_rate INT, -- 平均心率
max_heart_rate INT, -- 最大心率
data JSONB, -- 详细原始数据
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_training_records IS '训练记录表';
CREATE INDEX idx_training_records_user_id ON public.ak_training_records(user_id);
CREATE INDEX idx_training_records_plan_item_id ON public.ak_training_records(plan_item_id);
-- 体征监测数据表
CREATE TABLE public.ak_biometric_data (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 数据ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
device_id uuid REFERENCES public.ak_devices(id), -- 设备ID
data_type VARCHAR(32), -- 数据类型
value FLOAT, -- 数值
unit VARCHAR(16), -- 单位
measured_at TIMESTAMP WITH TIME ZONE, -- 测量时间
extra JSONB -- 扩展
);
COMMENT ON TABLE public.ak_biometric_data IS '体征监测数据表';
CREATE INDEX idx_biometric_data_user_id ON public.ak_biometric_data(user_id);
CREATE INDEX idx_biometric_data_device_id ON public.ak_biometric_data(device_id);
-- 视频分析结果表
CREATE TABLE public.ak_video_analysis (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 分析ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
device_id uuid REFERENCES public.ak_devices(id), -- 设备ID
record_id uuid REFERENCES public.ak_training_records(id), -- 训练记录ID
video_url TEXT, -- 视频地址
analysis_type VARCHAR(32), -- 分析类型
result JSONB, -- AI分析结果
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_video_analysis IS '视频分析结果表';
-- AI 评估报告表
CREATE TABLE public.ak_ai_reports (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 报告ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
record_id uuid REFERENCES public.ak_training_records(id), -- 训练记录ID
report_type VARCHAR(32), -- 报告类型
content JSONB, -- 评估内容
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_ai_reports IS 'AI评估报告表';
-- 消息/通知表
CREATE TABLE public.ak_notifications (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 通知ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
title VARCHAR(128), -- 标题
content TEXT, -- 内容
status VARCHAR(16) DEFAULT 'unread', -- 状态
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_notifications IS '消息/通知表';
-- 设备事件日志表
CREATE TABLE public.ak_device_events (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 日志ID
device_id uuid REFERENCES public.ak_devices(id) ON DELETE CASCADE, -- 设备ID
event_type VARCHAR(32), -- 事件类型
event_time TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 事件时间
data JSONB -- 事件数据
);
COMMENT ON TABLE public.ak_device_events IS '设备事件日志表';
-- 用户反馈表
CREATE TABLE public.ak_user_feedback (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 反馈ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
content TEXT, -- 反馈内容
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_user_feedback IS '用户反馈表';
-- 运动报告表
CREATE TABLE public.ak_sport_reports (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 报告ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
title VARCHAR(128) NOT NULL, -- 标题
content_md TEXT NOT NULL, -- Markdown内容
cover_url TEXT, -- 封面图片
visibility VARCHAR(16) DEFAULT 'private', -- 可见性
visible_until TIMESTAMP WITH TIME ZONE, -- 定时可见截止
allow_comment BOOLEAN DEFAULT true, -- 允许评论
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_sport_reports IS '运动报告表';
CREATE INDEX idx_sport_reports_user_id ON public.ak_sport_reports(user_id);
-- 运动报告分享记录表
CREATE TABLE public.ak_sport_report_shares (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 分享ID
report_id uuid REFERENCES public.ak_sport_reports(id) ON DELETE CASCADE, -- 报告ID
shared_by uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 分享人
shared_to uuid REFERENCES public.ak_users(id), -- 被分享人
share_type VARCHAR(16) DEFAULT 'link', -- 分享类型
share_token VARCHAR(64), -- 分享链接token
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
expired_at TIMESTAMP WITH TIME ZONE -- 过期时间
);
COMMENT ON TABLE public.ak_sport_report_shares IS '运动报告分享记录表';
CREATE INDEX idx_sport_report_shares_report_id ON public.ak_sport_report_shares(report_id);
CREATE INDEX idx_sport_report_shares_shared_by ON public.ak_sport_report_shares(shared_by);
-- 积分账户表
CREATE TABLE public.ak_user_points (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 账户ID
user_id uuid UNIQUE REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
total_points INT DEFAULT 0, -- 总积分
available_points INT DEFAULT 0, -- 可用积分
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_user_points IS '积分账户表';
-- 积分变动明细表
CREATE TABLE public.ak_user_point_logs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 明细ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
change_type VARCHAR(32) NOT NULL, -- 变动类型
points INT NOT NULL, -- 积分数
reason TEXT, -- 变动原因
related_id uuid, -- 关联业务ID
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_user_point_logs IS '积分变动明细表';
CREATE INDEX idx_user_point_logs_user_id ON public.ak_user_point_logs(user_id);
-- 积分规则表
CREATE TABLE public.ak_point_rules (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 规则ID
rule_name VARCHAR(64) NOT NULL, -- 规则名称
event_code VARCHAR(64) NOT NULL, -- 事件标识
points INT NOT NULL, -- 积分数
limit_per_day INT, -- 每日上限
description TEXT, -- 描述
is_active BOOLEAN DEFAULT true, -- 是否启用
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_point_rules IS '积分规则表';
-- 积分交易记录表
CREATE TABLE public.ak_point_transactions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 交易ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
transaction_type VARCHAR(32) NOT NULL, -- 交易类型 (earn, spend, refund等)
points INT NOT NULL, -- 积分数量
description TEXT, -- 交易描述
reference_id uuid, -- 关联业务ID (如训练记录ID、商品ID等)
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_point_transactions IS '积分交易记录表';
COMMENT ON COLUMN public.ak_point_transactions.transaction_type IS '交易类型earn(获得), spend(消费), refund(退还)等';
COMMENT ON COLUMN public.ak_point_transactions.points IS '积分数量,正数表示获得,负数表示消费';
COMMENT ON COLUMN public.ak_point_transactions.reference_id IS '关联业务ID如训练记录ID、商品ID等';
CREATE INDEX idx_point_transactions_user_id ON public.ak_point_transactions(user_id);
CREATE INDEX idx_point_transactions_type ON public.ak_point_transactions(transaction_type);
CREATE INDEX idx_point_transactions_created_at ON public.ak_point_transactions(created_at);
-- 作业表
CREATE TABLE public.ak_assignments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 作业ID
teacher_id uuid REFERENCES public.ak_users(id) ON DELETE SET NULL, -- 老师ID
title VARCHAR(128) NOT NULL, -- 作业标题
project_name VARCHAR(128), -- 作业所属项目/主题
status VARCHAR(32) DEFAULT 'active', -- 作业状态
start_time TIMESTAMP WITH TIME ZONE, -- 开始时间
end_time TIMESTAMP WITH TIME ZONE, -- 结束时间
description TEXT, -- 作业描述
class_id uuid REFERENCES public.ak_classes(id) ON DELETE CASCADE, -- 班级ID
due_date TIMESTAMP WITH TIME ZONE, -- 截止时间
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_assignments IS '作业表';
CREATE INDEX idx_assignments_teacher_id ON public.ak_assignments(teacher_id);
CREATE INDEX idx_assignments_class_id ON public.ak_assignments(class_id);
-- 作业提交表
CREATE TABLE public.ak_assignment_submissions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 提交ID
assignment_id uuid REFERENCES public.ak_assignments(id) ON DELETE CASCADE, -- 作业ID
student_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 学生ID
submit_time TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 提交时间
content_md TEXT, -- Markdown作业内容
attachment_url TEXT, -- 附件S3地址
score FLOAT, -- 分数
feedback TEXT, -- 反馈
status VARCHAR(16) DEFAULT 'submitted', -- 状态
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_assignment_submissions IS '作业提交表';
CREATE INDEX idx_assignment_submissions_assignment_id ON public.ak_assignment_submissions(assignment_id);
CREATE INDEX idx_assignment_submissions_student_id ON public.ak_assignment_submissions(student_id);
-- 作业统计表
CREATE TABLE public.ak_assignment_stats (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 统计ID
assignment_id uuid REFERENCES public.ak_assignments(id) ON DELETE CASCADE, -- 作业ID
total_students INT, -- 总学生数
submitted_count INT, -- 已提交数
reviewed_count INT, -- 已批改数
avg_score FLOAT, -- 平均分
last_updated TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_assignment_stats IS '作业统计表';
-- 教学资源表
CREATE TABLE public.ak_teaching_resources (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 资源ID
teacher_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 教师ID
type VARCHAR(32) NOT NULL, -- 资源类型video(视频), document(文档), plan(计划), image(图片)等
title VARCHAR(128) NOT NULL, -- 资源标题
description TEXT, -- 资源描述
content_url TEXT, -- 资源内容URL (S3地址等)
tags JSONB, -- 标签数组 (JSON格式)
is_public BOOLEAN DEFAULT false, -- 是否公开
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_teaching_resources IS '教学资源表';
COMMENT ON COLUMN public.ak_teaching_resources.type IS '资源类型video(视频), document(文档), plan(计划), image(图片)等';
COMMENT ON COLUMN public.ak_teaching_resources.tags IS '标签数组JSON格式存储';
COMMENT ON COLUMN public.ak_teaching_resources.is_public IS '是否公开public资源可被其他教师查看';
CREATE INDEX idx_teaching_resources_teacher_id ON public.ak_teaching_resources(teacher_id);
CREATE INDEX idx_teaching_resources_type ON public.ak_teaching_resources(type);
CREATE INDEX idx_teaching_resources_is_public ON public.ak_teaching_resources(is_public);
-- 课程安排表
CREATE TABLE public.ak_class_schedules (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 课程安排ID
teacher_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 教师ID
school_id uuid REFERENCES public.ak_schools(id) ON DELETE CASCADE, -- 学校ID
class_id uuid REFERENCES public.ak_classes(id) ON DELETE CASCADE, -- 班级ID
subject VARCHAR(64) NOT NULL, -- 科目名称
day_of_week INT NOT NULL CHECK (day_of_week >= 1 AND day_of_week <= 7), -- 星期几 (1=周一, 7=周日)
start_time TIME NOT NULL, -- 开始时间
end_time TIME NOT NULL, -- 结束时间
location VARCHAR(128), -- 上课地点
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_class_schedules IS '课程安排表';
COMMENT ON COLUMN public.ak_class_schedules.day_of_week IS '星期几1=周一2=周二...7=周日';
COMMENT ON COLUMN public.ak_class_schedules.subject IS '科目名称,如体育、数学等';
CREATE INDEX idx_class_schedules_teacher_id ON public.ak_class_schedules(teacher_id);
CREATE INDEX idx_class_schedules_school_id ON public.ak_class_schedules(school_id);
CREATE INDEX idx_class_schedules_class_id ON public.ak_class_schedules(class_id);
CREATE INDEX idx_class_schedules_day_of_week ON public.ak_class_schedules(day_of_week);
-- 互动记录表
CREATE TABLE public.ak_interactions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 互动ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID (发起互动的用户)
target_type VARCHAR(32) NOT NULL, -- 目标类型training_record, assignment_submission, teaching_resource等
target_id uuid NOT NULL, -- 目标ID (被互动的对象ID)
interaction_type VARCHAR(32) NOT NULL, -- 互动类型comment(评论), like(点赞), share(分享)等
content TEXT, -- 互动内容 (如评论内容,点赞时可为空)
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_interactions IS '互动记录表';
COMMENT ON COLUMN public.ak_interactions.target_type IS '目标类型training_record(训练记录), assignment_submission(作业提交), teaching_resource(教学资源)等';
COMMENT ON COLUMN public.ak_interactions.interaction_type IS '互动类型comment(评论), like(点赞), share(分享)等';
CREATE INDEX idx_interactions_user_id ON public.ak_interactions(user_id);
CREATE INDEX idx_interactions_target ON public.ak_interactions(target_type, target_id);
CREATE INDEX idx_interactions_type ON public.ak_interactions(interaction_type);
-- 老师权限管理表
CREATE TABLE public.ak_teacher_roles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 角色ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
school_id uuid REFERENCES public.ak_schools(id), -- 学校ID
class_id uuid REFERENCES public.ak_classes(id), -- 班级ID
role VARCHAR(32) DEFAULT 'teacher', -- 角色
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_teacher_roles IS '老师权限管理表';
CREATE INDEX idx_teacher_roles_user_id ON public.ak_teacher_roles(user_id);
CREATE INDEX idx_teacher_roles_school_id ON public.ak_teacher_roles(school_id);
CREATE INDEX idx_teacher_roles_class_id ON public.ak_teacher_roles(class_id);
-- 角色定义表
CREATE TABLE public.ak_roles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 角色ID
name VARCHAR(32) NOT NULL UNIQUE, -- 角色名称
description TEXT, -- 角色描述
level INT NOT NULL, -- 角色级别(数字越大权限越高)
is_system BOOLEAN DEFAULT false, -- 是否为系统角色
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_roles IS '角色定义表';
-- 插入基础角色
INSERT INTO public.ak_roles (name, description, level, is_system) VALUES
('student', '学生用户', 10, true),
('teacher', '教师用户', 20, true),
('school_admin', '学校管理员', 30, true),
('region_admin', '区域管理员', 40, true),
('system_admin', '系统管理员', 100, true);
-- 权限定义表
CREATE TABLE public.ak_permissions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 权限ID
code VARCHAR(64) NOT NULL UNIQUE, -- 权限代码
name VARCHAR(128) NOT NULL, -- 权限名称
description TEXT, -- 权限描述
resource_type VARCHAR(32) NOT NULL, -- 资源类型
action VARCHAR(32) NOT NULL, -- 操作类型create, read, update, delete, manage
is_system BOOLEAN DEFAULT false, -- 是否为系统权限
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_permissions IS '权限定义表';
-- 角色-权限关联表
CREATE TABLE public.ak_role_permissions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 关联ID
role_id uuid REFERENCES public.ak_roles(id) ON DELETE CASCADE, -- 角色ID
permission_id uuid REFERENCES public.ak_permissions(id) ON DELETE CASCADE, -- 权限ID
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_role_permissions IS '角色-权限关联表';
CREATE UNIQUE INDEX idx_role_permission_unique ON public.ak_role_permissions(role_id, permission_id);
CREATE INDEX idx_role_permissions_role_id ON public.ak_role_permissions(role_id);
CREATE INDEX idx_role_permissions_permission_id ON public.ak_role_permissions(permission_id);
-- 用户-角色关联表
CREATE TABLE public.ak_user_roles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 关联ID
user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 用户ID
role_id uuid REFERENCES public.ak_roles(id) ON DELETE CASCADE, -- 角色ID
scope_type VARCHAR(32), -- 作用域类型global, region, school, grade, class
scope_id uuid, -- 作用域ID
created_by uuid REFERENCES public.ak_users(id), -- 创建人
created_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 创建时间
);
COMMENT ON TABLE public.ak_user_roles IS '用户-角色关联表';
CREATE INDEX idx_user_roles_user_id ON public.ak_user_roles(user_id);
CREATE INDEX idx_user_roles_role_id ON public.ak_user_roles(role_id);
COMMENT ON COLUMN public.ak_user_roles.scope_type IS '作用域类型,指定角色在哪个范围内有效';
COMMENT ON COLUMN public.ak_user_roles.scope_id IS '作用域ID对应scope_type的记录ID';
-- 辅助函数:检查用户是否拥有指定权限
CREATE OR REPLACE FUNCTION public.has_permission(user_id uuid, permission_code text)
RETURNS boolean AS $$
DECLARE
has_perm boolean;
BEGIN
SELECT EXISTS(
SELECT 1 FROM public.ak_user_roles ur
JOIN public.ak_role_permissions rp ON ur.role_id = rp.role_id
JOIN public.ak_permissions p ON rp.permission_id = p.id
WHERE ur.user_id = $1 AND p.code = $2
) INTO has_perm;
RETURN has_perm;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 辅助函数:检查用户是否拥有指定作用域的权限
CREATE OR REPLACE FUNCTION public.has_scoped_permission(
user_id uuid,
permission_code text,
scope_type text,
scope_id uuid
)
RETURNS boolean AS $$
DECLARE
has_perm boolean;
BEGIN
-- 检查全局权限
SELECT EXISTS(
SELECT 1 FROM public.ak_user_roles ur
JOIN public.ak_role_permissions rp ON ur.role_id = rp.role_id
JOIN public.ak_permissions p ON rp.permission_id = p.id
WHERE ur.user_id = $1
AND p.code = $2
AND (ur.scope_type IS NULL OR ur.scope_type = 'global')
) INTO has_perm;
IF has_perm THEN
RETURN true;
END IF;
-- 检查特定作用域的权限
SELECT EXISTS(
SELECT 1 FROM public.ak_user_roles ur
JOIN public.ak_role_permissions rp ON ur.role_id = rp.role_id
JOIN public.ak_permissions p ON rp.permission_id = p.id
WHERE ur.user_id = $1
AND p.code = $2
AND ur.scope_type = $3
AND ur.scope_id = $4
) INTO has_perm;
RETURN has_perm;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 修改用户表触发器,自动分配学生角色
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
DECLARE
student_role_id uuid;
new_user_id uuid;
BEGIN
-- 创建基本用户记录
INSERT INTO public.ak_users (auth_id, username, email, role)
VALUES (new.id, new.email, new.email, 'student')
RETURNING id INTO new_user_id;
-- 获取学生角色ID
SELECT id INTO student_role_id FROM public.ak_roles WHERE name = 'student';
-- 分配学生角色
IF student_role_id IS NOT NULL THEN
INSERT INTO public.ak_user_roles (user_id, role_id, scope_type)
VALUES (new_user_id, student_role_id, 'global');
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 示例权限数据
INSERT INTO public.ak_permissions (code, name, resource_type, action, is_system, description) VALUES
-- 学生权限
('student.profile.read', '查看个人资料', 'profile', 'read', true, '查看自己的个人资料'),
('student.profile.update', '更新个人资料', 'profile', 'update', true, '更新自己的个人资料'),
('student.training.read', '查看训练记录', 'training', 'read', true, '查看自己的训练记录'),
('student.training.create', '创建训练记录', 'training', 'create', true, '创建自己的训练记录'),
-- 教师权限
('teacher.student.read', '查看学生资料', 'student', 'read', true, '查看所教班级学生的资料'),
('teacher.class.read', '查看班级信息', 'class', 'read', true, '查看所教班级信息'),
('teacher.assignment.manage', '管理作业', 'assignment', 'manage', true, '创建、修改班级作业'),
('teacher.report.read', '查看学生报告', 'report', 'read', true, '查看学生训练报告'),
-- 学校管理员权限
('school_admin.teacher.manage', '管理教师', 'teacher', 'manage', true, '管理学校教师账号'),
('school_admin.class.manage', '管理班级', 'class', 'manage', true, '管理学校班级'),
('school_admin.grade.manage', '管理年级', 'grade', 'manage', true, '管理学校年级'),
('school_admin.report.school', '学校统计报表', 'report', 'read', true, '查看学校统计报表'),
-- 系统管理员权限
('admin.system.manage', '系统管理', 'system', 'manage', true, '系统管理权限'),
('admin.users.manage', '用户管理', 'users', 'manage', true, '管理所有用户'),
('admin.roles.manage', '角色权限管理', 'roles', 'manage', true, '管理角色和权限');
-- 为角色分配基础权限
INSERT INTO public.ak_role_permissions (role_id, permission_id)
SELECT r.id, p.id FROM public.ak_roles r, public.ak_permissions p
WHERE
(r.name = 'student' AND p.code LIKE 'student.%') OR
(r.name = 'teacher' AND (p.code LIKE 'teacher.%' OR p.code LIKE 'student.%')) OR
(r.name = 'school_admin' AND (p.code LIKE 'school_admin.%' OR p.code LIKE 'teacher.%' OR p.code LIKE 'student.%')) OR
(r.name = 'system_admin' AND p.code LIKE '%');
-- 为所有数据表添加行级安全策略的辅助函数
CREATE OR REPLACE FUNCTION public.current_user_has_permission(permission_code text)
RETURNS boolean AS $$
DECLARE
user_id uuid;
BEGIN
-- 获取当前用户的ID
SELECT id INTO user_id FROM public.ak_users WHERE auth_id = auth.uid();
IF user_id IS NULL THEN
RETURN false;
END IF;
RETURN public.has_permission(user_id, permission_code);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 示例为班级表添加RLS策略
ALTER TABLE public.ak_classes ENABLE ROW LEVEL SECURITY;
-- 系统管理员和学校管理员可以管理所有班级
CREATE POLICY "管理员可以管理所有班级" ON public.ak_classes
USING (public.current_user_has_permission('admin.system.manage') OR
public.current_user_has_permission('school_admin.class.manage'));
-- 教师可以查看他们教授的班级
CREATE POLICY "教师可以查看所教授的班级" ON public.ak_classes
FOR SELECT
USING (EXISTS (
SELECT 1 FROM public.ak_teacher_roles tr
JOIN public.ak_users u ON tr.user_id = u.id
WHERE u.auth_id = auth.uid() AND tr.class_id = ak_classes.id
));
-- 添加i18n国际化支持
-- 插入基础语言
INSERT INTO public.ak_languages (code, name, native_name, is_active, is_default) VALUES
('zh-CN', 'Chinese (Simplified)', '简体中文', true, true),
('en-US', 'English (US)', 'English (US)', true, false);
-- 数据翻译基础表 - 用于继承
CREATE TABLE public.ak_translations (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- 翻译ID
record_id uuid NOT NULL, -- 关联记录ID
language_id uuid REFERENCES public.ak_languages(id) ON DELETE CASCADE, -- 语言ID
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_translations IS '数据翻译基础表(用于继承)';
CREATE UNIQUE INDEX idx_translations_record_language ON public.ak_translations (record_id, language_id);
CREATE INDEX idx_translations_language ON public.ak_translations (language_id);
-- 区域名称翻译表
CREATE TABLE public.ak_region_translations (
region_name VARCHAR(128) NOT NULL, -- 区域名称
CONSTRAINT ak_region_translations_pk PRIMARY KEY (id)
) INHERITS (public.ak_translations);
COMMENT ON TABLE public.ak_region_translations IS '区域名称翻译表';
ALTER TABLE public.ak_region_translations
ADD CONSTRAINT fk_region_translations_record
FOREIGN KEY (record_id)
REFERENCES public.ak_regions(id) ON DELETE CASCADE;
-- 学校名称翻译表
CREATE TABLE public.ak_school_translations (
school_name VARCHAR(128) NOT NULL, -- 学校名称
school_type VARCHAR(32), -- 学校类型
CONSTRAINT ak_school_translations_pk PRIMARY KEY (id)
) INHERITS (public.ak_translations);
COMMENT ON TABLE public.ak_school_translations IS '学校名称翻译表';
ALTER TABLE public.ak_school_translations
ADD CONSTRAINT fk_school_translations_record
FOREIGN KEY (record_id)
REFERENCES public.ak_schools(id) ON DELETE CASCADE;
-- 应用翻译表 (用于前端界面文本)
CREATE TABLE public.ak_app_translations (
key VARCHAR(255) NOT NULL, -- 翻译键
text TEXT NOT NULL, -- 翻译文本
language_id uuid REFERENCES public.ak_languages(id) ON DELETE CASCADE, -- 语言ID
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), -- 创建时间
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() -- 更新时间
);
COMMENT ON TABLE public.ak_app_translations IS '应用翻译表(前端界面文本)';
ALTER TABLE public.ak_app_translations ADD PRIMARY KEY (key, language_id);
CREATE INDEX idx_app_translations_language ON public.ak_app_translations (language_id);
-- 获取当前用户语言的函数
CREATE OR REPLACE FUNCTION public.get_user_language()
RETURNS uuid AS $$
DECLARE
lang_id uuid;
default_lang_id uuid;
BEGIN
-- 查询用户的首选语言
SELECT u.preferred_language INTO lang_id
FROM public.ak_users u
WHERE u.auth_id = auth.uid();
-- 如果没有设置或用户未登录,返回默认语言
IF lang_id IS NULL THEN
SELECT id INTO default_lang_id
FROM public.ak_languages
WHERE is_default = true
LIMIT 1;
RETURN default_lang_id;
END IF;
RETURN lang_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 获取翻译文本的函数 (通用版本)
CREATE OR REPLACE FUNCTION public.get_translation(
p_table_name text,
p_record_id uuid,
p_column_name text,
p_language_id uuid DEFAULT NULL
)
RETURNS text AS $$
DECLARE
result text;
lang_id uuid;
default_lang_id uuid;
query_text text;
BEGIN
-- 确定使用的语言ID
IF p_language_id IS NULL THEN
lang_id := public.get_user_language();
ELSE
lang_id := p_language_id;
END IF;
-- 构建查询语句动态获取翻译
query_text := 'SELECT t.' || quote_ident(p_column_name) ||
' FROM public.' || quote_ident('ak_' || p_table_name || '_translations') || ' t ' ||
'WHERE t.record_id = $1 AND t.language_id = $2';
-- 执行查询
EXECUTE query_text INTO result USING p_record_id, lang_id;
-- 如果没有找到翻译,尝试使用默认语言
IF result IS NULL THEN
SELECT id INTO default_lang_id
FROM public.ak_languages
WHERE is_default = true
LIMIT 1;
IF default_lang_id != lang_id THEN
EXECUTE query_text INTO result USING p_record_id, default_lang_id;
END IF;
END IF;
RETURN result;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 获取区域翻译名称的便捷函数
CREATE OR REPLACE FUNCTION public.get_region_name(
p_region_id uuid,
p_language_id uuid DEFAULT NULL
)
RETURNS text AS $$
DECLARE
translated_name text;
original_name text;
BEGIN
-- 尝试获取翻译
translated_name := public.get_translation('region', p_region_id, 'region_name', p_language_id);
-- 如果没有翻译,返回原始名称
IF translated_name IS NULL THEN
SELECT name INTO original_name
FROM public.ak_regions
WHERE id = p_region_id;
RETURN original_name;
END IF;
RETURN translated_name;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 获取学校翻译名称的便捷函数
CREATE OR REPLACE FUNCTION public.get_school_name(
p_school_id uuid,
p_language_id uuid DEFAULT NULL
)
RETURNS text AS $$
DECLARE
translated_name text;
original_name text;
BEGIN
-- 尝试获取翻译
translated_name := public.get_translation('school', p_school_id, 'school_name', p_language_id);
-- 如果没有翻译,返回原始名称
IF translated_name IS NULL THEN
SELECT name INTO original_name
FROM public.ak_schools
WHERE id = p_school_id;
RETURN original_name;
END IF;
RETURN translated_name;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 获取前端界面文本翻译的函数
CREATE OR REPLACE FUNCTION public.get_ui_text(
p_key text,
p_language_id uuid DEFAULT NULL
)
RETURNS text AS $$
DECLARE
result text;
lang_id uuid;
default_lang_id uuid;
BEGIN
-- 确定使用的语言ID
IF p_language_id IS NULL THEN
lang_id := public.get_user_language();
ELSE
lang_id := p_language_id;
END IF;
-- 获取翻译
SELECT text INTO result
FROM public.ak_app_translations
WHERE key = p_key AND language_id = lang_id;
-- 如果没有找到翻译,尝试使用默认语言
IF result IS NULL THEN
SELECT id INTO default_lang_id
FROM public.ak_languages
WHERE is_default = true
LIMIT 1;
IF default_lang_id != lang_id THEN
SELECT text INTO result
FROM public.ak_app_translations
WHERE key = p_key AND language_id = default_lang_id;
END IF;
END IF;
-- 如果仍然没有找到翻译,返回键名
IF result IS NULL THEN
RETURN p_key;
END IF;
RETURN result;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 创建翻译视图示例:区域多语言视图
CREATE OR REPLACE VIEW public.vw_regions_i18n AS
SELECT
r.id,
r.parent_id,
r.level,
r.created_at,
l.id as language_id,
l.code as language_code,
COALESCE(
rt.region_name,
r.name
) as name
FROM
public.ak_regions r
CROSS JOIN
public.ak_languages l
LEFT JOIN
public.ak_region_translations rt ON r.id = rt.record_id AND l.id = rt.language_id;
COMMENT ON VIEW public.vw_regions_i18n IS '区域多语言视图';
/*
i18n国际化系统说明
1. 核心设计:
- 通过ak_languages表定义支持的语言
- 主表存储默认语言(通常为中文)数据
- 翻译表以主表ID为外键存储其他语言的翻译
2. 翻译表结构:
- 基础翻译表ak_translations包含通用字段
- 特定内容的翻译表继承基础表并添加特定字段
- 组合索引确保每个记录的每种语言只有一个翻译
3. 用户语言偏好:
- 用户表增加preferred_language字段存储语言偏好
- get_user_language()函数获取当前用户的语言设置
4. 获取翻译的方式:
- 直接SQL查询翻译表
- 使用辅助函数如get_translation(), get_region_name()
- 通过多语言视图(如vw_regions_i18n)
5. 前端界面文本翻译:
- ak_app_translations表存储UI文本翻译
- get_ui_text()函数根据key获取对应语言的文本
*/