-- 运动训练监测与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获取对应语言的文本 */