-- Kids AI Posture Platform V1.0 database schema -- MySQL 8.x -- 目标:满足审计、幂等、治理、隐私与可追溯性要求 CREATE TABLE tenants ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(128) NOT NULL, type VARCHAR(32) NOT NULL DEFAULT 'organization', status VARCHAR(32) NOT NULL DEFAULT 'active', retention_days INT NOT NULL DEFAULT 1095, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CHECK (type IN ('organization','school','clinic','mall','other')), CHECK (status IN ('active','inactive','frozen','closed')), CHECK (retention_days BETWEEN 30 AND 3650) ); CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NULL, phone VARCHAR(32), phone_hash VARCHAR(128), wechat_openid VARCHAR(128), role VARCHAR(32) NOT NULL DEFAULT 'parent', status VARCHAR(32) NOT NULL DEFAULT 'active', consent_signed BOOLEAN NOT NULL DEFAULT FALSE, privacy_version VARCHAR(16) DEFAULT 'v1', last_login_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_users_tenant_role (tenant_id, role), UNIQUE KEY uk_users_phone_hash (phone_hash), UNIQUE KEY uk_users_wechat_openid (wechat_openid), CHECK (role IN ('parent','coach','org_admin','platform_admin')), CHECK (status IN ('active','inactive','blocked')) ); CREATE TABLE users_passwords ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, password_hash VARCHAR(256) NOT NULL, salt VARCHAR(64) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_user_password (user_id) ); CREATE TABLE children ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NULL, parent_user_id BIGINT NOT NULL, coach_user_id BIGINT NULL, name VARCHAR(64) NOT NULL, gender VARCHAR(16) NOT NULL DEFAULT 'unknown', birthday DATE NOT NULL, height DECIMAL(5,2), weight DECIMAL(5,2), contraindications TEXT, status VARCHAR(32) NOT NULL DEFAULT 'active', deleted_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_children_parent (parent_user_id), KEY idx_children_tenant_coach (tenant_id, coach_user_id), KEY idx_children_status (status), CHECK (gender IN ('male','female','unknown')), CHECK (status IN ('active','inactive','archived')), CHECK (height IS NULL OR (height > 20 AND height < 240)), CHECK (weight IS NULL OR (weight > 5 AND weight < 250)) ); CREATE TABLE child_measurements ( id BIGINT PRIMARY KEY AUTO_INCREMENT, child_id BIGINT NOT NULL, height DECIMAL(5,2), weight DECIMAL(5,2), source VARCHAR(32) NOT NULL DEFAULT 'manual', measured_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_measurements_child_time (child_id, measured_at), CHECK (source IN ('manual','device','coach','report')), CHECK (height IS NULL OR (height > 20 AND height < 240)), CHECK (weight IS NULL OR (weight > 5 AND weight < 250)) ); CREATE TABLE videos ( id BIGINT PRIMARY KEY AUTO_INCREMENT, child_id BIGINT NOT NULL, uploaded_by BIGINT NOT NULL, scene VARCHAR(64) NOT NULL, object_key VARCHAR(512) NOT NULL, duration_seconds INT, width INT, height INT, size_bytes BIGINT, codec VARCHAR(32), quality_result JSON, status VARCHAR(32) NOT NULL DEFAULT 'uploaded', archive_until_date DATE NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_videos_child_scene (child_id, scene), KEY idx_videos_status (status), UNIQUE KEY uk_videos_object_key (object_key), CHECK (scene IN ('front_posture','side_posture','squat','balance','gait','custom')), CHECK (status IN ('uploaded','transcoding','failed','ready','archived')), CHECK (duration_seconds IS NULL OR duration_seconds BETWEEN 1 AND 1800), CHECK (size_bytes IS NULL OR size_bytes BETWEEN 1 AND 10737418240) ); CREATE TABLE analysis_tasks ( id BIGINT PRIMARY KEY AUTO_INCREMENT, child_id BIGINT NOT NULL, video_id BIGINT NOT NULL, task_type VARCHAR(64) NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'CREATED', progress INT NOT NULL DEFAULT 0, model_version VARCHAR(64), error_code VARCHAR(64), error_message VARCHAR(512), result JSON, idempotency_key VARCHAR(128), retry_count INT NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, started_at TIMESTAMP NULL, finished_at TIMESTAMP NULL, UNIQUE KEY uk_analysis_idempotency (child_id, video_id, task_type, idempotency_key), KEY idx_analysis_child_status (child_id, status), KEY idx_analysis_video (video_id), KEY idx_analysis_created (created_at), CHECK (status IN ('CREATED','QUEUED','PROCESSING','SUCCEEDED','FAILED','CANCELLED')), CHECK (task_type IN ('posture_screening','movement_scoring','reassessment')), CHECK (progress BETWEEN 0 AND 100), CHECK (retry_count BETWEEN 0 AND 20) ); CREATE TABLE reports ( id BIGINT PRIMARY KEY AUTO_INCREMENT, child_id BIGINT NOT NULL, task_id BIGINT NOT NULL, report_type VARCHAR(64) NOT NULL DEFAULT 'posture_screening', risk_level VARCHAR(32) NOT NULL, summary TEXT NOT NULL, metrics JSON NOT NULL, recommendations JSON NOT NULL, disclaimer TEXT NOT NULL, status VARCHAR(32) NOT NULL DEFAULT 'draft', reviewed_by BIGINT NULL, reviewed_at TIMESTAMP NULL, published_at TIMESTAMP NULL, expires_at DATE NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_reports_task (task_id), KEY idx_reports_child_status (child_id, status), CHECK (risk_level IN ('low','medium','high','review_required')), CHECK (status IN ('draft','published','retracted','archived')) ); CREATE TABLE exercises ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(128) NOT NULL, category VARCHAR(64) NOT NULL, target_issue VARCHAR(128), difficulty VARCHAR(32) NOT NULL DEFAULT 'basic', duration_seconds INT NOT NULL DEFAULT 60, media_url VARCHAR(512), thumbnail_url VARCHAR(512), rules JSON, status VARCHAR(32) NOT NULL DEFAULT 'active', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_exercises_category (category, status), CHECK (difficulty IN ('basic','intermediate','advanced')), CHECK (status IN ('active','offline','deprecated')), CHECK (duration_seconds BETWEEN 20 AND 1800) ); CREATE TABLE training_plans ( id BIGINT PRIMARY KEY AUTO_INCREMENT, child_id BIGINT NOT NULL, report_id BIGINT NULL, coach_user_id BIGINT NULL, goal VARCHAR(256) NOT NULL, cycle_days INT NOT NULL DEFAULT 28, status VARCHAR(32) NOT NULL DEFAULT 'draft', plan_detail JSON NOT NULL, started_at TIMESTAMP NULL, ended_at TIMESTAMP NULL, deleted_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_training_plans_child_status (child_id, status), CHECK (cycle_days BETWEEN 7 AND 180), CHECK (status IN ('draft','active','completed','paused','cancelled')) ); CREATE TABLE training_records ( id BIGINT PRIMARY KEY AUTO_INCREMENT, plan_id BIGINT NOT NULL, child_id BIGINT NOT NULL, exercise_id BIGINT NOT NULL, completed BOOLEAN NOT NULL DEFAULT FALSE, score INT, duration_seconds INT, feedback JSON, note VARCHAR(512), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_training_records_plan (plan_id), KEY idx_training_records_child_time (child_id, created_at), CHECK (score IS NULL OR score BETWEEN 0 AND 100), CHECK (duration_seconds IS NULL OR duration_seconds BETWEEN 1 AND 7200) ); CREATE TABLE products ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NULL, name VARCHAR(128) NOT NULL, product_type VARCHAR(32) NOT NULL, price_cents INT NOT NULL, duration_days INT NULL, benefits JSON NOT NULL, limit_child INTEGER NOT NULL DEFAULT 1, status VARCHAR(32) NOT NULL DEFAULT 'active', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CHECK (product_type IN ('video_pack','training_plan','membership','assessment_bundle','course_package')), CHECK (status IN ('active','inactive','discontinued')), CHECK (price_cents >= 0), CHECK (duration_days IS NULL OR duration_days BETWEEN 1 AND 3650) ); CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, child_id BIGINT NULL, product_id BIGINT NOT NULL, amount_cents INT NOT NULL, pay_status VARCHAR(32) NOT NULL DEFAULT 'pending', payment_channel VARCHAR(32), paid_at TIMESTAMP NULL, canceled_at TIMESTAMP NULL, refund_status VARCHAR(32) DEFAULT 'none', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_orders_user_status (user_id, pay_status), KEY idx_orders_child (child_id), CHECK (pay_status IN ('pending','paid','failed','closed','refunding','refunded')), CHECK (refund_status IN ('none','requested','processing','completed','rejected')), CHECK (amount_cents >= 0) ); CREATE TABLE operation_leads ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NOT NULL, child_id BIGINT NULL, source VARCHAR(64) NOT NULL, stage VARCHAR(32) NOT NULL DEFAULT 'new', owner_user_id BIGINT NULL, score INT DEFAULT 0, priority TINYINT DEFAULT 3, next_follow_at TIMESTAMP NULL, note TEXT, touched_at TIMESTAMP NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_leads_tenant_stage (tenant_id, stage), KEY idx_leads_owner_next (owner_user_id, next_follow_at), CHECK (source IN ('wechat','h5','miniapp','partner','referral','offline_campaign')), CHECK (stage IN ('new','contacted','qualified','closed','lost','churned')), CHECK (priority BETWEEN 1 AND 5), CHECK (score BETWEEN 0 AND 100) ); CREATE TABLE audit_logs ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NULL, actor_user_id BIGINT NOT NULL, action VARCHAR(128) NOT NULL, resource_type VARCHAR(64) NOT NULL, resource_id BIGINT NULL, request_id VARCHAR(64), ip VARCHAR(64), user_agent VARCHAR(256), payload JSON, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_audit_actor_time (actor_user_id, created_at), KEY idx_audit_resource (resource_type, resource_id) ); CREATE TABLE data_retention_rules ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NULL, resource_type VARCHAR(64) NOT NULL, archive_after_days INT NOT NULL, delete_after_days INT NOT NULL, legal_basis VARCHAR(128), updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_retention (tenant_id, resource_type), CHECK (delete_after_days >= archive_after_days), CHECK (archive_after_days >= 30) ); CREATE TABLE api_idempotency_keys ( id BIGINT PRIMARY KEY AUTO_INCREMENT, scope VARCHAR(32) NOT NULL, idempotency_key VARCHAR(128) NOT NULL, request_path VARCHAR(255) NOT NULL, request_hash CHAR(64) NOT NULL, response_status INT, response_body JSON, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_idempotency_scope_key (scope, idempotency_key), KEY idx_idempotency_created (created_at) ); -- 运行时可选索引补充说明(按需执行) -- CREATE INDEX idx_analysis_status_updated ON analysis_tasks(status, updated_at); -- CREATE INDEX idx_reports_risk_level ON reports(risk_level, created_at); -- CREATE INDEX idx_orders_pay_status_updated ON orders(pay_status, updated_at);