-- =================================================================== -- 多语言AI驱动资讯系统数据库设计 -- 基于PostgreSQL 15+ -- 支持多语言内容、AI处理、智能推荐等功能 -- =================================================================== -- 启用必要的扩展 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "pg_trgm"; CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- =================================================================== -- 1. 基础数据表 -- =================================================================== -- 扩展现有语言表(基于现有ak_languages) DO $$ BEGIN -- 检查列是否存在,如果不存在则添加 IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_languages' AND column_name = 'ai_translation_enabled') THEN ALTER TABLE public.ak_languages ADD COLUMN ai_translation_enabled BOOLEAN DEFAULT true; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_languages' AND column_name = 'translation_quality_threshold') THEN ALTER TABLE public.ak_languages ADD COLUMN translation_quality_threshold FLOAT DEFAULT 0.8; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_languages' AND column_name = 'ai_provider_priority') THEN ALTER TABLE public.ak_languages ADD COLUMN ai_provider_priority JSONB DEFAULT '{"openai": 1, "google": 2, "baidu": 3}'; END IF; IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_languages' AND column_name = 'cultural_adaptation_rules') THEN ALTER TABLE public.ak_languages ADD COLUMN cultural_adaptation_rules JSONB; END IF; END $$; COMMENT ON COLUMN public.ak_languages.ai_translation_enabled IS 'AI翻译是否启用'; COMMENT ON COLUMN public.ak_languages.translation_quality_threshold IS '翻译质量阈值(0-1)'; COMMENT ON COLUMN public.ak_languages.ai_provider_priority IS 'AI提供商优先级配置'; COMMENT ON COLUMN public.ak_languages.cultural_adaptation_rules IS '文化适应性规则'; -- =================================================================== -- 2. 内容管理核心表 -- =================================================================== -- 内容源表 CREATE TABLE IF NOT EXISTS public.ak_content_sources ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(128) NOT NULL, type VARCHAR(32) NOT NULL, -- 'rss', 'api', 'crawler', 'social', 'manual' url TEXT, config JSONB, -- 配置信息(headers, auth, etc.) language_id uuid REFERENCES public.ak_languages(id), category VARCHAR(64), quality_weight FLOAT DEFAULT 1.0, -- 质量权重 crawl_frequency INTEGER DEFAULT 3600, -- 抓取频率(秒) last_crawled_at TIMESTAMP WITH TIME ZONE, is_active BOOLEAN DEFAULT true, ai_quality_threshold FLOAT DEFAULT 0.6, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_content_sources_type ON public.ak_content_sources(type); CREATE INDEX IF NOT EXISTS idx_content_sources_language ON public.ak_content_sources(language_id); CREATE INDEX IF NOT EXISTS idx_content_sources_active ON public.ak_content_sources(is_active); COMMENT ON TABLE public.ak_content_sources IS '内容源配置表'; -- 原始内容表 CREATE TABLE IF NOT EXISTS public.ak_raw_contents ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), source_id uuid REFERENCES public.ak_content_sources(id) ON DELETE CASCADE, external_id VARCHAR(256), -- 外部ID title TEXT NOT NULL, content TEXT NOT NULL, summary TEXT, author VARCHAR(128), source_url TEXT, published_at TIMESTAMP WITH TIME ZONE, language_detected VARCHAR(10), language_confidence FLOAT, content_hash VARCHAR(64), -- MD5哈希,用于去重 raw_metadata JSONB, processing_status VARCHAR(32) DEFAULT 'pending', -- 'pending', 'processing', 'completed', 'failed' quality_score FLOAT, duplicate_of uuid REFERENCES public.ak_raw_contents(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_raw_contents_source ON public.ak_raw_contents(source_id); CREATE INDEX IF NOT EXISTS idx_raw_contents_hash ON public.ak_raw_contents(content_hash); CREATE INDEX IF NOT EXISTS idx_raw_contents_status ON public.ak_raw_contents(processing_status); CREATE INDEX IF NOT EXISTS idx_raw_contents_language ON public.ak_raw_contents(language_detected); CREATE INDEX IF NOT EXISTS idx_raw_contents_published ON public.ak_raw_contents(published_at DESC); CREATE INDEX IF NOT EXISTS idx_raw_contents_quality ON public.ak_raw_contents(quality_score DESC); COMMENT ON TABLE public.ak_raw_contents IS '原始内容表'; -- 内容分类表 CREATE TABLE IF NOT EXISTS public.ak_content_categories ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name_key VARCHAR(64) NOT NULL UNIQUE, -- i18n key parent_id uuid REFERENCES public.ak_content_categories(id), level INTEGER DEFAULT 0, ai_keywords TEXT[], -- AI分类关键词 confidence_threshold FLOAT DEFAULT 0.8, sort_order INTEGER DEFAULT 0, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_content_categories_parent ON public.ak_content_categories(parent_id); CREATE INDEX IF NOT EXISTS idx_content_categories_level ON public.ak_content_categories(level); CREATE INDEX IF NOT EXISTS idx_content_categories_active ON public.ak_content_categories(is_active); COMMENT ON TABLE public.ak_content_categories IS '内容分类表'; -- 处理后的内容表 CREATE TABLE IF NOT EXISTS public.ak_contents ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), raw_content_id uuid REFERENCES public.ak_raw_contents(id) ON DELETE CASCADE, title TEXT NOT NULL, content TEXT NOT NULL, summary TEXT, author VARCHAR(128), source_url TEXT, original_language VARCHAR(10) NOT NULL, category_id uuid REFERENCES public.ak_content_categories(id), tags TEXT[], keywords TEXT[], entities JSONB, -- 命名实体识别结果 sentiment_score FLOAT, -- -1 to 1 readability_score FLOAT, -- 0 to 1 credibility_score FLOAT, -- 0 to 1 quality_score FLOAT NOT NULL, view_count INTEGER DEFAULT 0, like_count INTEGER DEFAULT 0, share_count INTEGER DEFAULT 0, comment_count INTEGER DEFAULT 0, published_at TIMESTAMP WITH TIME ZONE NOT NULL, featured_until TIMESTAMP WITH TIME ZONE, status VARCHAR(32) DEFAULT 'published', -- 'draft', 'published', 'archived', 'deleted' ai_processed_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -- 索引说明: -- 由于某些PostgreSQL版本对IMMUTABLE函数的严格要求, -- 暂时移除了全文搜索GIN索引以确保数据库结构能够成功创建。 -- 全文搜索功能可以通过以下方式实现: -- 1. 使用LIKE操作符进行简单文本搜索 -- 2. 使用ILIKE操作符进行不区分大小写的搜索 -- 3. 部署后单独添加全文搜索索引(参见fulltext_search_optional.sql) -- -- 简单搜索示例: -- SELECT * FROM ak_contents WHERE title ILIKE '%AI%'; -- SELECT * FROM ak_contents WHERE content ILIKE '%technology%'; -- 创建基本索引 (移除全文搜索GIN索引以避免IMMUTABLE错误) -- 删除可能存在的有问题的全文搜索索引 DROP INDEX IF EXISTS idx_contents_fts_title; DROP INDEX IF EXISTS idx_contents_fts_content; DROP INDEX IF EXISTS idx_contents_title_gin; DROP INDEX IF EXISTS idx_contents_content_gin; -- 使用普通的文本索引 (避免IMMUTABLE函数问题) CREATE INDEX IF NOT EXISTS idx_contents_title_text ON public.ak_contents(title); -- 注意:内容字段太长,不适合建立完整索引,全文搜索请使用可选脚本 CREATE INDEX IF NOT EXISTS idx_contents_category ON public.ak_contents(category_id); CREATE INDEX IF NOT EXISTS idx_contents_language ON public.ak_contents(original_language); CREATE INDEX IF NOT EXISTS idx_contents_published ON public.ak_contents(published_at DESC); CREATE INDEX IF NOT EXISTS idx_contents_quality ON public.ak_contents(quality_score DESC); CREATE INDEX IF NOT EXISTS idx_contents_status ON public.ak_contents(status); CREATE INDEX IF NOT EXISTS idx_contents_tags ON public.ak_contents USING gin(tags); COMMENT ON TABLE public.ak_contents IS '处理后的内容表'; -- =================================================================== -- 3. AI翻译相关表 -- =================================================================== -- 内容翻译表 CREATE TABLE IF NOT EXISTS public.ak_content_translations ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), content_id uuid REFERENCES public.ak_contents(id) ON DELETE CASCADE, language_id uuid REFERENCES public.ak_languages(id) ON DELETE CASCADE, title TEXT NOT NULL, content TEXT NOT NULL, summary TEXT, translation_method VARCHAR(32) DEFAULT 'ai', -- 'ai', 'human', 'hybrid' ai_provider VARCHAR(32), -- 'openai', 'google', 'baidu', 'custom' quality_score FLOAT, ai_confidence FLOAT, human_verified BOOLEAN DEFAULT false, human_verified_by uuid REFERENCES public.ak_users(id), human_verified_at TIMESTAMP WITH TIME ZONE, tokens_used INTEGER, processing_time_ms INTEGER, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), UNIQUE(content_id, language_id) ); CREATE INDEX IF NOT EXISTS idx_content_translations_content ON public.ak_content_translations(content_id); CREATE INDEX IF NOT EXISTS idx_content_translations_language ON public.ak_content_translations(language_id); CREATE INDEX IF NOT EXISTS idx_content_translations_method ON public.ak_content_translations(translation_method); CREATE INDEX IF NOT EXISTS idx_content_translations_quality ON public.ak_content_translations(quality_score DESC); CREATE INDEX IF NOT EXISTS idx_content_translations_verified ON public.ak_content_translations(human_verified); COMMENT ON TABLE public.ak_content_translations IS '内容翻译表'; -- AI翻译日志表 CREATE TABLE IF NOT EXISTS public.ak_ai_translation_logs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), content_id uuid REFERENCES public.ak_contents(id), source_language VARCHAR(10), target_language VARCHAR(10), ai_provider VARCHAR(32), -- 'openai', 'google', 'baidu' model_version VARCHAR(64), tokens_used INTEGER, cost_usd DECIMAL(10,4), processing_time_ms INTEGER, quality_score FLOAT, error_message TEXT, retry_count INTEGER DEFAULT 0, success BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_ai_translation_logs_content ON public.ak_ai_translation_logs(content_id); CREATE INDEX IF NOT EXISTS idx_ai_translation_logs_provider ON public.ak_ai_translation_logs(ai_provider); CREATE INDEX IF NOT EXISTS idx_ai_translation_logs_date ON public.ak_ai_translation_logs(created_at); CREATE INDEX IF NOT EXISTS idx_ai_translation_logs_success ON public.ak_ai_translation_logs(success); COMMENT ON TABLE public.ak_ai_translation_logs IS 'AI翻译日志表'; -- =================================================================== -- 4. AI内容分析表 -- =================================================================== -- AI生成的标签表 CREATE TABLE IF NOT EXISTS public.ak_ai_tags ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), content_id uuid REFERENCES public.ak_contents(id) ON DELETE CASCADE, tag_name VARCHAR(128) NOT NULL, confidence_score FLOAT NOT NULL, tag_type VARCHAR(32) DEFAULT 'topic', -- 'topic', 'entity', 'emotion', 'keyword', 'person', 'location', 'organization' ai_provider VARCHAR(32), language VARCHAR(10), created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_ai_tags_content ON public.ak_ai_tags(content_id); CREATE INDEX IF NOT EXISTS idx_ai_tags_type ON public.ak_ai_tags(tag_type); CREATE INDEX IF NOT EXISTS idx_ai_tags_confidence ON public.ak_ai_tags(confidence_score DESC); CREATE INDEX IF NOT EXISTS idx_ai_tags_name ON public.ak_ai_tags(tag_name); COMMENT ON TABLE public.ak_ai_tags IS 'AI生成的标签表'; -- 内容分析结果表 CREATE TABLE IF NOT EXISTS public.ak_content_analysis ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), content_id uuid REFERENCES public.ak_contents(id) ON DELETE CASCADE, category_id uuid REFERENCES public.ak_content_categories(id), sentiment_score FLOAT, -- -1 to 1 sentiment_label VARCHAR(32), -- 'positive', 'negative', 'neutral' readability_score FLOAT, -- 0 to 1 credibility_score FLOAT, -- 0 to 1 toxicity_score FLOAT, -- 0 to 1 keywords JSONB, entities JSONB, -- 命名实体识别结果 topics JSONB, -- 主题提取结果 summary TEXT, key_phrases TEXT[], ai_provider VARCHAR(32), model_version VARCHAR(64), processing_time_ms INTEGER, ai_processed_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_content_analysis_content ON public.ak_content_analysis(content_id); CREATE INDEX IF NOT EXISTS idx_content_analysis_category ON public.ak_content_analysis(category_id); CREATE INDEX IF NOT EXISTS idx_content_analysis_sentiment ON public.ak_content_analysis(sentiment_score); CREATE INDEX IF NOT EXISTS idx_content_analysis_credibility ON public.ak_content_analysis(credibility_score); COMMENT ON TABLE public.ak_content_analysis IS '内容分析结果表'; -- =================================================================== -- 5. 用户行为和推荐系统 -- =================================================================== -- 用户行为记录表 CREATE TABLE IF NOT EXISTS public.ak_user_behaviors ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, content_id uuid REFERENCES public.ak_contents(id) ON DELETE CASCADE, behavior_type VARCHAR(32) NOT NULL, -- 'view', 'like', 'share', 'comment', 'save', 'click' behavior_data JSONB, -- 额外的行为数据 duration_seconds INTEGER, -- 阅读时长 scroll_percentage FLOAT, -- 滚动百分比 device_type VARCHAR(32), -- 'mobile', 'tablet', 'desktop' source VARCHAR(64), -- 来源渠道 session_id VARCHAR(128), ip_address INET, user_agent TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_user_behaviors_user ON public.ak_user_behaviors(user_id); CREATE INDEX IF NOT EXISTS idx_user_behaviors_content ON public.ak_user_behaviors(content_id); CREATE INDEX IF NOT EXISTS idx_user_behaviors_type ON public.ak_user_behaviors(behavior_type); CREATE INDEX IF NOT EXISTS idx_user_behaviors_date ON public.ak_user_behaviors(created_at); CREATE INDEX IF NOT EXISTS idx_user_behaviors_session ON public.ak_user_behaviors(session_id); COMMENT ON TABLE public.ak_user_behaviors IS '用户行为记录表'; -- 用户兴趣画像表 CREATE TABLE IF NOT EXISTS public.ak_user_profiles ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE UNIQUE, interests JSONB, -- 兴趣标签和权重 categories JSONB, -- 分类偏好 languages JSONB, -- 语言偏好 reading_speed_wpm INTEGER, -- 阅读速度(每分钟字数) preferred_content_length VARCHAR(32), -- 'short', 'medium', 'long' active_hours JSONB, -- 活跃时间段 device_preferences JSONB, interaction_patterns JSONB, last_active_at TIMESTAMP WITH TIME ZONE, updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_user_profiles_user ON public.ak_user_profiles(user_id); CREATE INDEX IF NOT EXISTS idx_user_profiles_updated ON public.ak_user_profiles(updated_at); COMMENT ON TABLE public.ak_user_profiles IS '用户兴趣画像表'; -- 推荐记录表 CREATE TABLE IF NOT EXISTS public.ak_recommendations ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, content_id uuid REFERENCES public.ak_contents(id) ON DELETE CASCADE, algorithm_type VARCHAR(32), -- 'collaborative', 'content_based', 'hybrid', 'ai_ranking' score FLOAT NOT NULL, reason TEXT, -- AI生成的推荐理由 position INTEGER, -- 推荐位置 shown_at TIMESTAMP WITH TIME ZONE, clicked_at TIMESTAMP WITH TIME ZONE, feedback_score INTEGER, -- 用户反馈分数 1-5 feedback_reason TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_recommendations_user ON public.ak_recommendations(user_id); CREATE INDEX IF NOT EXISTS idx_recommendations_content ON public.ak_recommendations(content_id); CREATE INDEX IF NOT EXISTS idx_recommendations_algorithm ON public.ak_recommendations(algorithm_type); CREATE INDEX IF NOT EXISTS idx_recommendations_score ON public.ak_recommendations(score DESC); CREATE INDEX IF NOT EXISTS idx_recommendations_shown ON public.ak_recommendations(shown_at); COMMENT ON TABLE public.ak_recommendations IS '推荐记录表'; -- =================================================================== -- 6. AI聊天助手相关表 -- =================================================================== -- AI聊天会话表 CREATE TABLE IF NOT EXISTS public.ak_chat_sessions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid REFERENCES public.ak_users(id) ON DELETE CASCADE, session_name VARCHAR(128), language VARCHAR(10) DEFAULT 'zh-CN', context JSONB, -- 会话上下文 ai_model VARCHAR(64) DEFAULT 'gpt-4', total_messages INTEGER DEFAULT 0, total_tokens INTEGER DEFAULT 0, cost_usd DECIMAL(10,4) DEFAULT 0, last_message_at TIMESTAMP WITH TIME ZONE, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_chat_sessions_user ON public.ak_chat_sessions(user_id); CREATE INDEX IF NOT EXISTS idx_chat_sessions_active ON public.ak_chat_sessions(is_active); CREATE INDEX IF NOT EXISTS idx_chat_sessions_last_message ON public.ak_chat_sessions(last_message_at DESC); COMMENT ON TABLE public.ak_chat_sessions IS 'AI聊天会话表'; -- AI聊天消息表 CREATE TABLE IF NOT EXISTS public.ak_chat_messages ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), session_id uuid REFERENCES public.ak_chat_sessions(id) ON DELETE CASCADE, message_type VARCHAR(32), -- 'user', 'assistant', 'system' content TEXT NOT NULL, intent VARCHAR(64), -- 'search', 'recommend', 'question', 'general' attachments JSONB, -- 附件信息 ai_provider VARCHAR(32), tokens_used INTEGER, processing_time_ms INTEGER, cost_usd DECIMAL(10,6), feedback_score INTEGER, -- 1-5 feedback_reason TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_chat_messages_session ON public.ak_chat_messages(session_id); CREATE INDEX IF NOT EXISTS idx_chat_messages_type ON public.ak_chat_messages(message_type); CREATE INDEX IF NOT EXISTS idx_chat_messages_intent ON public.ak_chat_messages(intent); CREATE INDEX IF NOT EXISTS idx_chat_messages_date ON public.ak_chat_messages(created_at); COMMENT ON TABLE public.ak_chat_messages IS 'AI聊天消息表'; -- =================================================================== -- 7. 系统监控和成本控制表 -- =================================================================== -- AI服务使用统计表 CREATE TABLE IF NOT EXISTS public.ak_ai_usage_stats ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid REFERENCES public.ak_users(id), service_type VARCHAR(32), -- 'translation', 'analysis', 'chat', 'recommendation' ai_provider VARCHAR(32), -- 'openai', 'google', 'baidu' model_name VARCHAR(64), tokens_used INTEGER, requests_count INTEGER DEFAULT 1, cost_usd DECIMAL(10,6), processing_time_ms INTEGER, success_count INTEGER DEFAULT 0, error_count INTEGER DEFAULT 0, date_bucket DATE, -- 用于按日统计 created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), UNIQUE(service_type, ai_provider, date_bucket) ); -- 创建复合索引用于统计查询 CREATE INDEX IF NOT EXISTS idx_ai_usage_stats_user_date ON public.ak_ai_usage_stats(user_id, date_bucket); CREATE INDEX IF NOT EXISTS idx_ai_usage_stats_service_date ON public.ak_ai_usage_stats(service_type, date_bucket); CREATE INDEX IF NOT EXISTS idx_ai_usage_stats_provider_date ON public.ak_ai_usage_stats(ai_provider, date_bucket); COMMENT ON TABLE public.ak_ai_usage_stats IS 'AI服务使用统计表'; -- 成本控制限额表 CREATE TABLE IF NOT EXISTS public.ak_cost_limits ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), limit_type VARCHAR(32), -- 'daily', 'monthly', 'user_daily', 'user_monthly' limit_scope VARCHAR(32), -- 'global', 'user', 'service' scope_id uuid, -- 用户ID或服务ID limit_amount DECIMAL(10,2), current_usage DECIMAL(10,2) DEFAULT 0, period_start DATE, period_end DATE, is_active BOOLEAN DEFAULT true, alert_threshold DECIMAL(4,3) DEFAULT 0.8, -- 80%时告警 created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_cost_limits_type ON public.ak_cost_limits(limit_type); CREATE INDEX IF NOT EXISTS idx_cost_limits_scope ON public.ak_cost_limits(limit_scope, scope_id); CREATE INDEX IF NOT EXISTS idx_cost_limits_active ON public.ak_cost_limits(is_active); COMMENT ON TABLE public.ak_cost_limits IS '成本控制限额表'; -- =================================================================== -- 8. 内容质量和审核表 -- =================================================================== -- 内容质量评估历史表 CREATE TABLE IF NOT EXISTS public.ak_content_quality_history ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), content_id uuid REFERENCES public.ak_contents(id) ON DELETE CASCADE, quality_type VARCHAR(32), -- 'ai_initial', 'ai_recheck', 'human_review' quality_score FLOAT, ai_provider VARCHAR(32), model_version VARCHAR(64), assessment_criteria JSONB, reviewer_id uuid REFERENCES public.ak_users(id), notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_quality_history_content ON public.ak_content_quality_history(content_id); CREATE INDEX IF NOT EXISTS idx_quality_history_type ON public.ak_content_quality_history(quality_type); CREATE INDEX IF NOT EXISTS idx_quality_history_score ON public.ak_content_quality_history(quality_score DESC); COMMENT ON TABLE public.ak_content_quality_history IS '内容质量评估历史表'; -- 人工审核队列表 CREATE TABLE IF NOT EXISTS public.ak_review_queue ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), content_id uuid REFERENCES public.ak_contents(id) ON DELETE CASCADE, translation_id uuid REFERENCES public.ak_content_translations(id) ON DELETE CASCADE, review_type VARCHAR(32), -- 'quality', 'translation', 'content_policy' priority INTEGER DEFAULT 5, -- 1-10, 1最高 reason TEXT, ai_confidence FLOAT, requested_by uuid REFERENCES public.ak_users(id), assigned_to uuid REFERENCES public.ak_users(id), status VARCHAR(32) DEFAULT 'pending', -- 'pending', 'in_progress', 'completed', 'rejected' review_notes TEXT, review_result JSONB, completed_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_review_queue_status ON public.ak_review_queue(status); CREATE INDEX IF NOT EXISTS idx_review_queue_priority ON public.ak_review_queue(priority DESC); CREATE INDEX IF NOT EXISTS idx_review_queue_assigned ON public.ak_review_queue(assigned_to); CREATE INDEX IF NOT EXISTS idx_review_queue_type ON public.ak_review_queue(review_type); COMMENT ON TABLE public.ak_review_queue IS '人工审核队列表'; -- =================================================================== -- 9. 创建视图和函数 -- =================================================================== -- 多语言内容视图 CREATE OR REPLACE VIEW public.vw_multilingual_contents AS SELECT c.id, c.title as original_title, c.content as original_content, c.summary as original_summary, c.original_language, c.category_id, cc.name_key as category_name, c.quality_score, c.published_at, c.status, c.view_count, c.like_count, c.share_count, -- 聚合翻译信息 COALESCE( json_agg( json_build_object( 'language', l.code, 'title', ct.title, 'content', ct.content, 'summary', ct.summary, 'quality_score', ct.quality_score, 'translation_method', ct.translation_method, 'human_verified', ct.human_verified ) ) FILTER (WHERE ct.id IS NOT NULL), '[]'::json ) as translations, -- 聚合AI标签 COALESCE( json_agg( json_build_object( 'tag_name', aitag.tag_name, 'confidence', aitag.confidence_score, 'type', aitag.tag_type ) ) FILTER (WHERE aitag.id IS NOT NULL), '[]'::json ) as ai_tags FROM public.ak_contents c LEFT JOIN public.ak_content_categories cc ON c.category_id = cc.id LEFT JOIN public.ak_content_translations ct ON c.id = ct.content_id LEFT JOIN public.ak_languages l ON ct.language_id = l.id LEFT JOIN public.ak_ai_tags aitag ON c.id = aitag.content_id WHERE c.status = 'published' GROUP BY c.id, cc.name_key; COMMENT ON VIEW public.vw_multilingual_contents IS '多语言内容聚合视图'; -- 用户推荐内容函数 CREATE OR REPLACE FUNCTION public.get_user_recommendations( p_user_id uuid, p_language varchar(10) DEFAULT 'zh-CN', p_limit integer DEFAULT 20 ) RETURNS TABLE ( content_id uuid, title text, summary text, category text, quality_score float, recommendation_score float, recommendation_reason text, published_at timestamp with time zone ) AS $$ BEGIN RETURN QUERY SELECT c.id, COALESCE(ct.title, c.title) as title, COALESCE(ct.summary, c.summary) as summary, cc.name_key as category, c.quality_score, r.score as recommendation_score, r.reason as recommendation_reason, c.published_at FROM public.ak_recommendations r JOIN public.ak_contents c ON r.content_id = c.id LEFT JOIN public.ak_content_categories cc ON c.category_id = cc.id LEFT JOIN public.ak_content_translations ct ON c.id = ct.content_id AND ct.language_id = (SELECT id FROM public.ak_languages WHERE code = p_language) WHERE r.user_id = p_user_id AND c.status = 'published' AND r.shown_at IS NULL -- 未展示过的推荐 ORDER BY r.score DESC, c.published_at DESC LIMIT p_limit; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION public.get_user_recommendations IS '获取用户个性化推荐内容'; -- AI翻译成本统计函数 CREATE OR REPLACE FUNCTION public.get_ai_cost_stats( p_start_date date DEFAULT CURRENT_DATE - INTERVAL '30 days', p_end_date date DEFAULT CURRENT_DATE ) RETURNS TABLE ( ai_provider varchar(32), service_type varchar(32), total_requests bigint, total_tokens bigint, total_cost decimal(10,2), avg_processing_time_ms float, success_rate float ) AS $$ BEGIN RETURN QUERY SELECT s.ai_provider, s.service_type, SUM(s.requests_count) as total_requests, SUM(s.tokens_used) as total_tokens, SUM(s.cost_usd)::decimal(10,2) as total_cost, AVG(s.processing_time_ms) as avg_processing_time_ms, CASE WHEN SUM(s.requests_count) > 0 THEN (SUM(s.success_count)::float / SUM(s.requests_count)::float * 100) ELSE 0 END as success_rate FROM public.ak_ai_usage_stats s WHERE s.date_bucket BETWEEN p_start_date AND p_end_date GROUP BY s.ai_provider, s.service_type ORDER BY total_cost DESC; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION public.get_ai_cost_stats IS 'AI服务成本统计分析'; -- =================================================================== -- 10. 触发器和自动化处理 -- =================================================================== -- 自动更新用户画像的触发器函数 CREATE OR REPLACE FUNCTION public.update_user_profile_on_behavior() RETURNS TRIGGER AS $$ BEGIN -- 异步更新用户画像(通过队列系统) INSERT INTO public.ak_user_behaviors ( user_id, content_id, behavior_type, behavior_data, duration_seconds, scroll_percentage, device_type, session_id, created_at ) VALUES ( NEW.user_id, NEW.content_id, NEW.behavior_type, NEW.behavior_data, NEW.duration_seconds, NEW.scroll_percentage, NEW.device_type, NEW.session_id, NEW.created_at ); -- 更新用户画像的最后活跃时间 INSERT INTO public.ak_user_profiles (user_id, last_active_at, profile_updated_at) VALUES (NEW.user_id, NEW.created_at, NEW.created_at) ON CONFLICT (user_id) DO UPDATE SET last_active_at = NEW.created_at, profile_updated_at = NEW.created_at; RETURN NEW; END; $$ LANGUAGE plpgsql; -- 创建触发器 DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_update_user_profile') THEN CREATE TRIGGER trigger_update_user_profile AFTER INSERT ON public.ak_user_behaviors FOR EACH ROW EXECUTE FUNCTION public.update_user_profile_on_behavior(); END IF; END $$; -- 自动记录AI使用统计的触发器函数 CREATE OR REPLACE FUNCTION public.record_ai_usage_stats() RETURNS TRIGGER AS $$ BEGIN INSERT INTO public.ak_ai_usage_stats ( service_type, ai_provider, model_name, tokens_used, cost_usd, processing_time_ms, success_count, error_count, date_bucket, created_at ) VALUES ( 'translation', NEW.ai_provider, 'default', NEW.tokens_used, NEW.cost_usd, NEW.processing_time_ms, CASE WHEN NEW.success THEN 1 ELSE 0 END, CASE WHEN NEW.success THEN 0 ELSE 1 END, CURRENT_DATE, NEW.created_at ) ON CONFLICT (service_type, ai_provider, date_bucket) DO UPDATE SET tokens_used = ak_ai_usage_stats.tokens_used + EXCLUDED.tokens_used, requests_count = ak_ai_usage_stats.requests_count + 1, cost_usd = ak_ai_usage_stats.cost_usd + EXCLUDED.cost_usd, processing_time_ms = (ak_ai_usage_stats.processing_time_ms + EXCLUDED.processing_time_ms) / 2, success_count = ak_ai_usage_stats.success_count + EXCLUDED.success_count, error_count = ak_ai_usage_stats.error_count + EXCLUDED.error_count; RETURN NEW; END; $$ LANGUAGE plpgsql; -- 为翻译日志表创建触发器 DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_record_translation_stats') THEN CREATE TRIGGER trigger_record_translation_stats AFTER INSERT ON public.ak_ai_translation_logs FOR EACH ROW EXECUTE FUNCTION public.record_ai_usage_stats(); END IF; END $$; -- =================================================================== -- 11. 初始化基础数据 -- =================================================================== -- 插入基础内容分类 INSERT INTO public.ak_content_categories (name_key, ai_keywords, level) VALUES ('news.politics', ARRAY['政治', '政府', '政策', '选举', '政党'], 0), ('news.economy', ARRAY['经济', '金融', '股市', '贸易', '投资'], 0), ('news.technology', ARRAY['科技', '人工智能', '互联网', '软件', '硬件'], 0), ('news.sports', ARRAY['体育', '足球', '篮球', '奥运', '比赛'], 0), ('news.entertainment', ARRAY['娱乐', '明星', '电影', '音乐', '综艺'], 0), ('news.health', ARRAY['健康', '医疗', '疾病', '养生', '医学'], 0), ('news.education', ARRAY['教育', '学校', '考试', '培训', '学习'], 0), ('news.international', ARRAY['国际', '外交', '全球', '世界', '国外'], 0) ON CONFLICT DO NOTHING; -- 插入AI服务成本控制默认配置 INSERT INTO public.ak_cost_limits (limit_type, limit_scope, limit_amount, period_start, period_end) VALUES ('daily', 'global', 1000.00, CURRENT_DATE, CURRENT_DATE + INTERVAL '1 day'), ('monthly', 'global', 25000.00, DATE_TRUNC('month', CURRENT_DATE), DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month'), ('user_daily', 'user', 50.00, CURRENT_DATE, CURRENT_DATE + INTERVAL '1 day'), ('user_monthly', 'user', 500.00, DATE_TRUNC('month', CURRENT_DATE), DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month') ON CONFLICT DO NOTHING; -- =================================================================== -- 12. 权限设置 (RLS - Row Level Security) -- =================================================================== -- 启用RLS ALTER TABLE public.ak_content_translations ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_user_behaviors ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_user_profiles ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_recommendations ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_chat_sessions ENABLE ROW LEVEL SECURITY; ALTER TABLE public.ak_chat_messages ENABLE ROW LEVEL SECURITY; -- 内容翻译访问策略 DROP POLICY IF EXISTS "content_translations_select_policy" ON public.ak_content_translations; CREATE POLICY "content_translations_select_policy" ON public.ak_content_translations FOR SELECT USING (true); -- 所有人可读 DROP POLICY IF EXISTS "content_translations_insert_policy" ON public.ak_content_translations; CREATE POLICY "content_translations_insert_policy" ON public.ak_content_translations FOR INSERT WITH CHECK (auth.uid() IS NOT NULL); -- 认证用户可插入 -- 用户行为策略 DROP POLICY IF EXISTS "user_behaviors_select_policy" ON public.ak_user_behaviors; CREATE POLICY "user_behaviors_select_policy" ON public.ak_user_behaviors FOR SELECT USING (user_id = auth.uid()); -- 只能查看自己的行为 DROP POLICY IF EXISTS "user_behaviors_insert_policy" ON public.ak_user_behaviors; CREATE POLICY "user_behaviors_insert_policy" ON public.ak_user_behaviors FOR INSERT WITH CHECK (user_id = auth.uid()); -- 只能插入自己的行为 -- 用户画像策略 DROP POLICY IF EXISTS "user_profiles_select_policy" ON public.ak_user_profiles; CREATE POLICY "user_profiles_select_policy" ON public.ak_user_profiles FOR SELECT USING (user_id = auth.uid()); -- 只能查看自己的画像 DROP POLICY IF EXISTS "user_profiles_update_policy" ON public.ak_user_profiles; CREATE POLICY "user_profiles_update_policy" ON public.ak_user_profiles FOR ALL USING (user_id = auth.uid()); -- 只能修改自己的画像 -- 推荐记录策略 DROP POLICY IF EXISTS "recommendations_select_policy" ON public.ak_recommendations; CREATE POLICY "recommendations_select_policy" ON public.ak_recommendations FOR SELECT USING (user_id = auth.uid()); -- 只能查看自己的推荐 -- 聊天会话策略 DROP POLICY IF EXISTS "chat_sessions_policy" ON public.ak_chat_sessions; CREATE POLICY "chat_sessions_policy" ON public.ak_chat_sessions FOR ALL USING (user_id = auth.uid()); -- 只能操作自己的会话 -- 聊天消息策略 DROP POLICY IF EXISTS "chat_messages_policy" ON public.ak_chat_messages; CREATE POLICY "chat_messages_policy" ON public.ak_chat_messages FOR ALL USING ( session_id IN ( SELECT id FROM public.ak_chat_sessions WHERE user_id = auth.uid() ) ); -- 只能操作自己会话的消息 -- =================================================================== -- 创建完成 -- =================================================================== -- =================================================================== -- 11. 专题系统表 -- =================================================================== -- 专题表 CREATE TABLE IF NOT EXISTS public.ak_topics ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(255) NOT NULL, description TEXT, topic_type VARCHAR(32) NOT NULL DEFAULT 'series', -- 'breaking', 'trending', 'series', 'analysis', 'guide', 'interview', 'report', 'timeline' status VARCHAR(32) DEFAULT 'active', -- 'draft', 'active', 'featured', 'archived', 'closed' cover_image_url TEXT, creator_id uuid, -- 创建者ID editor_id uuid, -- 编辑者ID content_count INTEGER DEFAULT 0, view_count INTEGER DEFAULT 0, like_count INTEGER DEFAULT 0, share_count INTEGER DEFAULT 0, comment_count INTEGER DEFAULT 0, subscriber_count INTEGER DEFAULT 0, last_content_added_at TIMESTAMP WITH TIME ZONE, featured_until TIMESTAMP WITH TIME ZONE, meta_keywords TEXT[], meta_description TEXT, seo_slug VARCHAR(255), priority_level INTEGER DEFAULT 0, -- 优先级,数字越大优先级越高 tags TEXT[], created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -- 专题索引 CREATE INDEX IF NOT EXISTS idx_topics_type ON public.ak_topics(topic_type); CREATE INDEX IF NOT EXISTS idx_topics_status ON public.ak_topics(status); CREATE INDEX IF NOT EXISTS idx_topics_featured ON public.ak_topics(featured_until); CREATE INDEX IF NOT EXISTS idx_topics_created ON public.ak_topics(created_at DESC); CREATE INDEX IF NOT EXISTS idx_topics_updated ON public.ak_topics(updated_at DESC); CREATE INDEX IF NOT EXISTS idx_topics_priority ON public.ak_topics(priority_level DESC); CREATE INDEX IF NOT EXISTS idx_topics_view_count ON public.ak_topics(view_count DESC); CREATE INDEX IF NOT EXISTS idx_topics_content_count ON public.ak_topics(content_count DESC); CREATE INDEX IF NOT EXISTS idx_topics_slug ON public.ak_topics(seo_slug); COMMENT ON TABLE public.ak_topics IS '专题表'; COMMENT ON COLUMN public.ak_topics.topic_type IS '专题类型:突发事件、热门话题、系列专题、深度分析等'; COMMENT ON COLUMN public.ak_topics.status IS '专题状态:草稿、活跃、精选、归档、关闭'; COMMENT ON COLUMN public.ak_topics.priority_level IS '优先级:数字越大优先级越高'; -- 专题内容关联表 CREATE TABLE IF NOT EXISTS public.ak_topic_contents ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), topic_id uuid NOT NULL REFERENCES public.ak_topics(id) ON DELETE CASCADE, content_id uuid NOT NULL REFERENCES public.ak_contents(id) ON DELETE CASCADE, display_order INTEGER DEFAULT 0, editor_note TEXT, -- 编辑说明 is_featured BOOLEAN DEFAULT false, -- 是否为专题精选内容 added_by uuid, -- 添加者ID added_at TIMESTAMP WITH TIME ZONE DEFAULT now(), created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), UNIQUE(topic_id, content_id) ); -- 专题内容关联索引 CREATE INDEX IF NOT EXISTS idx_topic_contents_topic ON public.ak_topic_contents(topic_id, display_order); CREATE INDEX IF NOT EXISTS idx_topic_contents_content ON public.ak_topic_contents(content_id); CREATE INDEX IF NOT EXISTS idx_topic_contents_featured ON public.ak_topic_contents(is_featured); CREATE INDEX IF NOT EXISTS idx_topic_contents_added ON public.ak_topic_contents(added_at DESC); COMMENT ON TABLE public.ak_topic_contents IS '专题内容关联表'; COMMENT ON COLUMN public.ak_topic_contents.display_order IS '显示顺序,数字越小越靠前'; COMMENT ON COLUMN public.ak_topic_contents.editor_note IS '编辑对该内容在专题中的说明'; -- 专题订阅表 CREATE TABLE IF NOT EXISTS public.ak_topic_subscriptions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), topic_id uuid NOT NULL REFERENCES public.ak_topics(id) ON DELETE CASCADE, user_id uuid NOT NULL, notification_enabled BOOLEAN DEFAULT true, subscribed_at TIMESTAMP WITH TIME ZONE DEFAULT now(), last_notified_at TIMESTAMP WITH TIME ZONE, UNIQUE(topic_id, user_id) ); -- 专题订阅索引 CREATE INDEX IF NOT EXISTS idx_topic_subscriptions_topic ON public.ak_topic_subscriptions(topic_id); CREATE INDEX IF NOT EXISTS idx_topic_subscriptions_user ON public.ak_topic_subscriptions(user_id); CREATE INDEX IF NOT EXISTS idx_topic_subscriptions_notification ON public.ak_topic_subscriptions(notification_enabled); COMMENT ON TABLE public.ak_topic_subscriptions IS '专题订阅表'; -- =================================================================== -- 12. 评论系统表 -- =================================================================== -- 评论表 CREATE TABLE IF NOT EXISTS public.ak_comments ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), target_type VARCHAR(20) NOT NULL, -- 'content', 'topic' target_id uuid NOT NULL, -- 目标内容/专题ID parent_id uuid REFERENCES public.ak_comments(id) ON DELETE CASCADE, -- 父评论ID,NULL表示顶级评论 author_id uuid NOT NULL, -- 评论作者ID author_name VARCHAR(100) NOT NULL, -- 作者显示名称 author_avatar TEXT, -- 作者头像URL content TEXT NOT NULL, -- 评论内容 content_html TEXT, -- 处理后的HTML内容(支持@提及、链接等) status VARCHAR(20) DEFAULT 'active', -- 'active', 'hidden', 'deleted', 'pending_review', 'rejected' like_count INTEGER DEFAULT 0, -- 点赞数 dislike_count INTEGER DEFAULT 0, -- 踩数 reply_count INTEGER DEFAULT 0, -- 回复数 level INTEGER DEFAULT 0, -- 评论层级:0=顶级评论,1=一级回复,2=二级回复,最多3级 thread_path TEXT, -- 线程路径,如: "1/5/12",用于追踪评论关系 is_pinned BOOLEAN DEFAULT false, -- 是否置顶 is_author_reply BOOLEAN DEFAULT false, -- 是否为内容作者回复 quality_score FLOAT DEFAULT 0.5, -- 评论质量评分 0-1 sentiment_score FLOAT, -- 情感倾向评分 -1到1 language_detected VARCHAR(10), -- 检测到的语言 ip_address INET, -- IP地址 user_agent TEXT, -- 用户代理 device_info JSONB, -- 设备信息 moderation_flags TEXT[], -- 审核标记 ai_analysis JSONB, -- AI分析结果 created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), deleted_at TIMESTAMP WITH TIME ZONE -- 软删除时间 ); -- 评论索引 CREATE INDEX IF NOT EXISTS idx_comments_target ON public.ak_comments(target_type, target_id, status, created_at DESC); CREATE INDEX IF NOT EXISTS idx_comments_parent ON public.ak_comments(parent_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_comments_author ON public.ak_comments(author_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_comments_thread ON public.ak_comments(thread_path); CREATE INDEX IF NOT EXISTS idx_comments_level ON public.ak_comments(level); CREATE INDEX IF NOT EXISTS idx_comments_status ON public.ak_comments(status); CREATE INDEX IF NOT EXISTS idx_comments_like_count ON public.ak_comments(like_count DESC); CREATE INDEX IF NOT EXISTS idx_comments_quality ON public.ak_comments(quality_score DESC); CREATE INDEX IF NOT EXISTS idx_comments_pinned ON public.ak_comments(is_pinned); CREATE INDEX IF NOT EXISTS idx_comments_created ON public.ak_comments(created_at DESC); COMMENT ON TABLE public.ak_comments IS '评论表'; COMMENT ON COLUMN public.ak_comments.target_type IS '评论目标类型:content=内容评论,topic=专题评论'; COMMENT ON COLUMN public.ak_comments.level IS '评论层级:0=顶级,1=一级回复,2=二级回复,最多3级'; COMMENT ON COLUMN public.ak_comments.thread_path IS '线程路径,用于快速查询评论树结构'; COMMENT ON COLUMN public.ak_comments.quality_score IS '评论质量评分,由AI算法计算'; -- 评论点赞表 CREATE TABLE IF NOT EXISTS public.ak_comment_reactions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), comment_id uuid NOT NULL REFERENCES public.ak_comments(id) ON DELETE CASCADE, user_id uuid NOT NULL, reaction_type VARCHAR(20) NOT NULL DEFAULT 'like', -- 'like', 'dislike', 'love', 'laugh', 'angry', 'sad' created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), UNIQUE(comment_id, user_id, reaction_type) ); -- 评论点赞索引 CREATE INDEX IF NOT EXISTS idx_comment_reactions_comment ON public.ak_comment_reactions(comment_id, reaction_type); CREATE INDEX IF NOT EXISTS idx_comment_reactions_user ON public.ak_comment_reactions(user_id, created_at DESC); COMMENT ON TABLE public.ak_comment_reactions IS '评论反应表(点赞、踩等)'; -- 评论举报表 CREATE TABLE IF NOT EXISTS public.ak_comment_reports ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), comment_id uuid NOT NULL REFERENCES public.ak_comments(id) ON DELETE CASCADE, reporter_id uuid NOT NULL, report_type VARCHAR(32) NOT NULL, -- 'spam', 'inappropriate', 'harassment', 'misinformation', 'copyright', 'other' report_reason TEXT, status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'reviewed', 'resolved', 'dismissed' reviewed_by uuid, -- 审核员ID reviewed_at TIMESTAMP WITH TIME ZONE, review_notes TEXT, action_taken VARCHAR(50), -- 处理措施 created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -- 评论举报索引 CREATE INDEX IF NOT EXISTS idx_comment_reports_comment ON public.ak_comment_reports(comment_id); CREATE INDEX IF NOT EXISTS idx_comment_reports_reporter ON public.ak_comment_reports(reporter_id); CREATE INDEX IF NOT EXISTS idx_comment_reports_status ON public.ak_comment_reports(status); CREATE INDEX IF NOT EXISTS idx_comment_reports_type ON public.ak_comment_reports(report_type); CREATE INDEX IF NOT EXISTS idx_comment_reports_created ON public.ak_comment_reports(created_at DESC); COMMENT ON TABLE public.ak_comment_reports IS '评论举报表'; -- 评论审核队列表 CREATE TABLE IF NOT EXISTS public.ak_comment_moderation_queue ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), comment_id uuid NOT NULL REFERENCES public.ak_comments(id) ON DELETE CASCADE, reason VARCHAR(100) NOT NULL, -- 进入队列的原因 priority_level INTEGER DEFAULT 0, -- 优先级 assigned_to uuid, -- 分配给的审核员 status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'in_progress', 'completed' ai_risk_score FLOAT, -- AI计算的风险评分 ai_recommendations JSONB, -- AI审核建议 created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), assigned_at TIMESTAMP WITH TIME ZONE, completed_at TIMESTAMP WITH TIME ZONE ); -- 评论审核队列索引 CREATE INDEX IF NOT EXISTS idx_comment_moderation_queue_status ON public.ak_comment_moderation_queue(status, priority_level DESC); CREATE INDEX IF NOT EXISTS idx_comment_moderation_queue_assigned ON public.ak_comment_moderation_queue(assigned_to, status); CREATE INDEX IF NOT EXISTS idx_comment_moderation_queue_risk ON public.ak_comment_moderation_queue(ai_risk_score DESC); COMMENT ON TABLE public.ak_comment_moderation_queue IS '评论审核队列表'; -- =================================================================== -- 13. 转发和收藏系统表 -- =================================================================== -- 内容收藏表 CREATE TABLE IF NOT EXISTS public.ak_content_favorites ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL, target_type VARCHAR(20) NOT NULL, -- 'content', 'topic' target_id uuid NOT NULL, -- 目标内容/专题ID folder_id uuid, -- 收藏夹ID,NULL表示默认收藏夹 notes TEXT, -- 用户收藏备注 tags TEXT[], -- 用户自定义标签 is_public BOOLEAN DEFAULT false, -- 是否公开收藏 created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), UNIQUE(user_id, target_type, target_id) ); -- 收藏索引 CREATE INDEX IF NOT EXISTS idx_content_favorites_user ON public.ak_content_favorites(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_content_favorites_target ON public.ak_content_favorites(target_type, target_id); CREATE INDEX IF NOT EXISTS idx_content_favorites_folder ON public.ak_content_favorites(folder_id); CREATE INDEX IF NOT EXISTS idx_content_favorites_public ON public.ak_content_favorites(is_public, created_at DESC); CREATE INDEX IF NOT EXISTS idx_content_favorites_tags ON public.ak_content_favorites USING GIN(tags); COMMENT ON TABLE public.ak_content_favorites IS '内容收藏表'; COMMENT ON COLUMN public.ak_content_favorites.target_type IS '收藏目标类型:content=内容收藏,topic=专题收藏'; COMMENT ON COLUMN public.ak_content_favorites.folder_id IS '收藏夹ID,NULL表示默认收藏夹'; COMMENT ON COLUMN public.ak_content_favorites.is_public IS '是否公开收藏,用于社交功能'; -- 收藏夹表 CREATE TABLE IF NOT EXISTS public.ak_favorite_folders ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL, name VARCHAR(100) NOT NULL, description TEXT, icon VARCHAR(32), -- 收藏夹图标 color VARCHAR(7), -- 收藏夹颜色 (HEX) display_order INTEGER DEFAULT 0, is_default BOOLEAN DEFAULT false, -- 是否为默认收藏夹 is_public BOOLEAN DEFAULT false, -- 是否公开收藏夹 item_count INTEGER DEFAULT 0, -- 收藏项目数量 created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -- 收藏夹索引 CREATE INDEX IF NOT EXISTS idx_favorite_folders_user ON public.ak_favorite_folders(user_id, display_order); CREATE INDEX IF NOT EXISTS idx_favorite_folders_public ON public.ak_favorite_folders(is_public, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_favorite_folders_user_default ON public.ak_favorite_folders(user_id, is_default); COMMENT ON TABLE public.ak_favorite_folders IS '收藏夹表'; COMMENT ON COLUMN public.ak_favorite_folders.is_default IS '每个用户只能有一个默认收藏夹'; COMMENT ON COLUMN public.ak_favorite_folders.item_count IS '收藏项目数量,由触发器维护'; -- 内容转发表 CREATE TABLE IF NOT EXISTS public.ak_content_shares ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL, -- 转发用户ID target_type VARCHAR(20) NOT NULL, -- 'content', 'topic', 'comment' target_id uuid NOT NULL, -- 目标ID share_type VARCHAR(20) NOT NULL DEFAULT 'forward', -- 'forward', 'quote', 'repost', 'link' share_platform VARCHAR(32), -- 分享平台: 'internal', 'wechat', 'weibo', 'twitter', 'facebook', 'email', 'copy_link' share_content TEXT, -- 转发时的附加内容/评论 share_title VARCHAR(255), -- 自定义分享标题 original_author_id uuid, -- 原作者ID parent_share_id uuid REFERENCES public.ak_content_shares(id), -- 转发的转发(转发链) share_level INTEGER DEFAULT 0, -- 转发层级,0为原创,1为一级转发,以此类推 reach_count INTEGER DEFAULT 0, -- 触达人数 click_count INTEGER DEFAULT 0, -- 点击数 like_count INTEGER DEFAULT 0, -- 点赞数 comment_count INTEGER DEFAULT 0, -- 评论数 reshare_count INTEGER DEFAULT 0, -- 再转发数 is_deleted BOOLEAN DEFAULT false, -- 是否删除 deleted_reason VARCHAR(100), -- 删除原因 ip_address INET, -- IP地址 user_agent TEXT, -- 用户代理 device_info JSONB, -- 设备信息 geo_location POINT, -- 地理位置 created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), deleted_at TIMESTAMP WITH TIME ZONE ); -- 转发索引 CREATE INDEX IF NOT EXISTS idx_content_shares_user ON public.ak_content_shares(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_content_shares_target ON public.ak_content_shares(target_type, target_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_content_shares_platform ON public.ak_content_shares(share_platform, created_at DESC); CREATE INDEX IF NOT EXISTS idx_content_shares_parent ON public.ak_content_shares(parent_share_id); CREATE INDEX IF NOT EXISTS idx_content_shares_level ON public.ak_content_shares(share_level); CREATE INDEX IF NOT EXISTS idx_content_shares_stats ON public.ak_content_shares(like_count DESC, reshare_count DESC, created_at DESC); -- 移除带WHERE条件的索引以避免IMMUTABLE错误 -- CREATE INDEX IF NOT EXISTS idx_content_shares_hot ON public.ak_content_shares(reach_count DESC, click_count DESC) WHERE created_at >= now() - interval '7 days'; CREATE INDEX IF NOT EXISTS idx_content_shares_hot ON public.ak_content_shares(reach_count DESC, click_count DESC); COMMENT ON TABLE public.ak_content_shares IS '内容转发分享表'; COMMENT ON COLUMN public.ak_content_shares.share_type IS '分享类型:forward=转发,quote=引用,repost=重发,link=链接分享'; COMMENT ON COLUMN public.ak_content_shares.share_level IS '转发层级,用于追踪转发链条'; COMMENT ON COLUMN public.ak_content_shares.reach_count IS '触达人数,用于分析传播效果'; -- 分享统计表(用于分析热门内容传播) CREATE TABLE IF NOT EXISTS public.ak_share_analytics ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), target_type VARCHAR(20) NOT NULL, target_id uuid NOT NULL, date_recorded DATE NOT NULL DEFAULT CURRENT_DATE, platform VARCHAR(32) NOT NULL, share_count INTEGER DEFAULT 0, unique_sharers INTEGER DEFAULT 0, -- 独立分享用户数 total_reach INTEGER DEFAULT 0, -- 总触达人数 total_clicks INTEGER DEFAULT 0, -- 总点击数 avg_share_level FLOAT DEFAULT 0, -- 平均转发层级 viral_coefficient FLOAT DEFAULT 0, -- 病毒系数(每个分享带来的新分享数) created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), UNIQUE(target_type, target_id, date_recorded, platform) ); -- 分享统计索引 CREATE INDEX IF NOT EXISTS idx_share_analytics_target ON public.ak_share_analytics(target_type, target_id, date_recorded DESC); CREATE INDEX IF NOT EXISTS idx_share_analytics_platform ON public.ak_share_analytics(platform, date_recorded DESC); CREATE INDEX IF NOT EXISTS idx_share_analytics_viral ON public.ak_share_analytics(viral_coefficient DESC, date_recorded DESC); CREATE INDEX IF NOT EXISTS idx_share_analytics_reach ON public.ak_share_analytics(total_reach DESC, date_recorded DESC); COMMENT ON TABLE public.ak_share_analytics IS '分享统计分析表'; COMMENT ON COLUMN public.ak_share_analytics.viral_coefficient IS '病毒传播系数,用于分析内容传播力'; -- 收藏分享互动表(用户可以分享自己的收藏夹) CREATE TABLE IF NOT EXISTS public.ak_favorite_shares ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), folder_id uuid NOT NULL REFERENCES public.ak_favorite_folders(id) ON DELETE CASCADE, shared_by uuid NOT NULL, -- 分享者ID share_type VARCHAR(20) DEFAULT 'link', -- 'link', 'embed', 'public' share_code VARCHAR(32) UNIQUE, -- 分享码 access_password VARCHAR(100), -- 访问密码(可选) expire_at TIMESTAMP WITH TIME ZONE, -- 过期时间 view_count INTEGER DEFAULT 0, clone_count INTEGER DEFAULT 0, -- 被复制次数 is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -- 收藏分享索引 CREATE INDEX IF NOT EXISTS idx_favorite_shares_folder ON public.ak_favorite_shares(folder_id); CREATE INDEX IF NOT EXISTS idx_favorite_shares_user ON public.ak_favorite_shares(shared_by, created_at DESC); CREATE INDEX IF NOT EXISTS idx_favorite_shares_code ON public.ak_favorite_shares(share_code); CREATE INDEX IF NOT EXISTS idx_favorite_shares_active ON public.ak_favorite_shares(is_active, created_at DESC); COMMENT ON TABLE public.ak_favorite_shares IS '收藏夹分享表'; COMMENT ON COLUMN public.ak_favorite_shares.share_code IS '分享码,用于生成分享链接'; COMMENT ON COLUMN public.ak_favorite_shares.clone_count IS '收藏夹被复制到其他用户的次数'; -- =================================================================== -- 扩展现有表以支持收藏功能 -- =================================================================== -- 为内容表添加收藏计数字段 DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_contents' AND column_name = 'favorite_count') THEN ALTER TABLE public.ak_contents ADD COLUMN favorite_count INTEGER DEFAULT 0; RAISE NOTICE '已为 ak_contents 表添加 favorite_count 字段'; END IF; END $$; -- 为专题表添加收藏计数字段 DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'ak_topics' AND column_name = 'favorite_count') THEN ALTER TABLE public.ak_topics ADD COLUMN favorite_count INTEGER DEFAULT 0; RAISE NOTICE '已为 ak_topics 表添加 favorite_count 字段'; END IF; END $$; -- =================================================================== -- 初始化数据和版本信息 -- =================================================================== -- 初始化专题类型数据 INSERT INTO public.ak_content_categories (name_key, parent_id, level, ai_keywords, sort_order) VALUES ('topic.breaking', NULL, 0, ARRAY['突发', '紧急', '最新', 'breaking'], 1), ('topic.trending', NULL, 0, ARRAY['热门', '趋势', '流行', 'trending'], 2), ('topic.series', NULL, 0, ARRAY['系列', '专题', '连载', 'series'], 3), ('topic.analysis', NULL, 0, ARRAY['分析', '解读', '深度', 'analysis'], 4), ('topic.guide', NULL, 0, ARRAY['指南', '教程', '攻略', 'guide'], 5), ('topic.interview', NULL, 0, ARRAY['专访', '访谈', '对话', 'interview'], 6), ('topic.report', NULL, 0, ARRAY['报告', '调查', '研究', 'report'], 7), ('topic.timeline', NULL, 0, ARRAY['时间轴', '追踪', '进展', 'timeline'], 8) ON CONFLICT (name_key) DO NOTHING; -- 创建数据库版本记录表 CREATE TABLE IF NOT EXISTS public.ak_database_versions ( id SERIAL PRIMARY KEY, version VARCHAR(32) NOT NULL UNIQUE, description TEXT, applied_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -- 插入版本信息 INSERT INTO public.ak_database_versions (version, description) VALUES ('1.2.0', '添加专题系统、评论系统、转发分享和收藏功能支持') ON CONFLICT (version) DO NOTHING; -- 输出创建完成信息 DO $$ BEGIN RAISE NOTICE '==================================================================='; RAISE NOTICE '多语言AI驱动资讯系统数据库创建完成!'; RAISE NOTICE '版本: 1.2.0'; RAISE NOTICE '创建时间: %', now(); RAISE NOTICE '==================================================================='; RAISE NOTICE '已创建的主要表:'; RAISE NOTICE '- 内容管理: ak_content_sources, ak_raw_contents, ak_contents'; RAISE NOTICE '- AI翻译: ak_content_translations, ak_ai_translation_logs'; RAISE NOTICE '- 内容分析: ak_ai_tags, ak_content_analysis'; RAISE NOTICE '- 用户行为: ak_user_behaviors, ak_user_profiles'; RAISE NOTICE '- 推荐系统: ak_recommendations'; RAISE NOTICE '- AI聊天: ak_chat_sessions, ak_chat_messages'; RAISE NOTICE '- 专题系统: ak_topics, ak_topic_contents, ak_topic_subscriptions'; RAISE NOTICE '- 评论系统: ak_comments, ak_comment_reactions, ak_comment_reports, ak_comment_moderation_queue'; RAISE NOTICE '- 收藏系统: ak_content_favorites, ak_favorite_folders, ak_favorite_shares'; RAISE NOTICE '- 转发分享: ak_content_shares, ak_share_analytics'; RAISE NOTICE '- 系统监控: ak_ai_usage_stats, ak_cost_limits'; RAISE NOTICE '- 质量控制: ak_content_quality_history, ak_review_queue'; RAISE NOTICE '==================================================================='; RAISE NOTICE '新增功能特性:'; RAISE NOTICE '- 专题管理:支持8种专题类型,内容聚合和订阅功能'; RAISE NOTICE '- 评论系统:多层级评论,点赞举报,自动审核机制'; RAISE NOTICE '- 收藏功能:个人收藏夹,分类管理,公开分享'; RAISE NOTICE '- 转发分享:多平台分享,转发链追踪,传播分析'; RAISE NOTICE '- 自动化:统计维护,触发器更新,数据完整性保证'; RAISE NOTICE '- 数据分析:分享统计,传播效果,用户行为分析'; RAISE NOTICE '==================================================================='; RAISE NOTICE '数据库部署完成!可以开始使用转发和收藏功能。'; END $$; -- =================================================================== -- 14. 多语言分类名称表 -- =================================================================== -- 多语言分类名称表 CREATE TABLE IF NOT EXISTS public.ak_content_category_translations ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), category_id uuid NOT NULL REFERENCES public.ak_content_categories(id) ON DELETE CASCADE, language_code VARCHAR(10) NOT NULL, -- 如 'zh-CN', 'en', 'ja' name VARCHAR(128) NOT NULL, description TEXT, UNIQUE(category_id, language_code) ); COMMENT ON TABLE public.ak_content_category_translations IS '内容分类多语言名称表'; COMMENT ON COLUMN public.ak_content_category_translations.language_code IS '语言代码,如zh-CN、en、ja'; COMMENT ON COLUMN public.ak_content_category_translations.name IS '该语言下的分类名称'; COMMENT ON COLUMN public.ak_content_category_translations.description IS '该语言下的分类描述'; -- 示例插入多语言分类名称 INSERT INTO public.ak_content_category_translations (category_id, language_code, name, description) SELECT id, 'zh-CN', CASE name_key WHEN 'news.politics' THEN '政治' WHEN 'news.economy' THEN '经济' WHEN 'news.technology' THEN '科技' WHEN 'news.sports' THEN '体育' WHEN 'news.entertainment' THEN '娱乐' WHEN 'news.health' THEN '健康' WHEN 'news.education' THEN '教育' WHEN 'news.international' THEN '国际' ELSE name_key END, NULL FROM public.ak_content_categories ON CONFLICT (category_id, language_code) DO NOTHING; INSERT INTO public.ak_content_category_translations (category_id, language_code, name, description) SELECT id, 'en', CASE name_key WHEN 'news.politics' THEN 'Politics' WHEN 'news.economy' THEN 'Economy' WHEN 'news.technology' THEN 'Technology' WHEN 'news.sports' THEN 'Sports' WHEN 'news.entertainment' THEN 'Entertainment' WHEN 'news.health' THEN 'Health' WHEN 'news.education' THEN 'Education' WHEN 'news.international' THEN 'International' ELSE name_key END, NULL FROM public.ak_content_categories ON CONFLICT (category_id, language_code) DO NOTHING; -- 查询示例:获取指定语言的所有分类 -- SELECT c.id, t.name, t.description FROM public.ak_content_categories c -- LEFT JOIN public.ak_content_category_translations t ON c.id = t.category_id AND t.language_code = 'zh-CN'; -- =================================================================== -- 15. 全局配置和公司信息系统 -- =================================================================== -- 全局配置表(支持多语言) CREATE TABLE IF NOT EXISTS public.ak_global_configs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), config_key VARCHAR(128) NOT NULL UNIQUE, -- 配置键,如 'company.name', 'company.slogan' config_group VARCHAR(64) NOT NULL, -- 配置分组,如 'company', 'system', 'ui' config_type VARCHAR(32) NOT NULL DEFAULT 'text', -- 'text', 'number', 'boolean', 'json', 'url', 'email', 'phone' default_value TEXT, -- 默认值(通常是中文或英文) validation_rules JSONB, -- 验证规则,如长度限制、正则表达式等 is_public BOOLEAN DEFAULT true, -- 是否为公开配置(前端可访问) is_translatable BOOLEAN DEFAULT true, -- 是否支持多语言翻译 description TEXT, -- 配置说明 display_order INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -- 全局配置索引 CREATE INDEX IF NOT EXISTS idx_global_configs_group ON public.ak_global_configs(config_group); CREATE INDEX IF NOT EXISTS idx_global_configs_key ON public.ak_global_configs(config_key); CREATE INDEX IF NOT EXISTS idx_global_configs_public ON public.ak_global_configs(is_public); CREATE INDEX IF NOT EXISTS idx_global_configs_translatable ON public.ak_global_configs(is_translatable); COMMENT ON TABLE public.ak_global_configs IS '全局配置表'; COMMENT ON COLUMN public.ak_global_configs.config_key IS '配置键名,采用点分割命名如company.name'; COMMENT ON COLUMN public.ak_global_configs.config_group IS '配置分组,便于管理和分类'; COMMENT ON COLUMN public.ak_global_configs.is_public IS '是否为前端可访问的公开配置'; COMMENT ON COLUMN public.ak_global_configs.is_translatable IS '是否需要多语言支持'; -- 全局配置多语言翻译表 CREATE TABLE IF NOT EXISTS public.ak_global_config_translations ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), config_id uuid NOT NULL REFERENCES public.ak_global_configs(id) ON DELETE CASCADE, language_code VARCHAR(10) NOT NULL, -- 语言代码 translated_value TEXT NOT NULL, -- 翻译后的值 is_machine_translated BOOLEAN DEFAULT false, -- 是否为机器翻译 translator_id uuid, -- 翻译员ID(人工翻译时) translation_quality FLOAT DEFAULT 1.0, -- 翻译质量评分 last_reviewed_at TIMESTAMP WITH TIME ZONE, -- 最后审核时间 created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now(), UNIQUE(config_id, language_code) ); -- 全局配置翻译索引 CREATE INDEX IF NOT EXISTS idx_global_config_translations_config ON public.ak_global_config_translations(config_id); CREATE INDEX IF NOT EXISTS idx_global_config_translations_language ON public.ak_global_config_translations(language_code); CREATE INDEX IF NOT EXISTS idx_global_config_translations_quality ON public.ak_global_config_translations(translation_quality DESC); COMMENT ON TABLE public.ak_global_config_translations IS '全局配置多语言翻译表'; -- 配置变更历史表(用于审计和回滚) CREATE TABLE IF NOT EXISTS public.ak_config_change_history ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), config_id uuid NOT NULL REFERENCES public.ak_global_configs(id) ON DELETE CASCADE, language_code VARCHAR(10), -- NULL表示修改默认值 old_value TEXT, new_value TEXT, change_type VARCHAR(32) NOT NULL, -- 'create', 'update', 'delete', 'translate' changed_by uuid, -- 操作员ID change_reason TEXT, -- 变更原因 ip_address INET, user_agent TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); -- 配置变更历史索引 CREATE INDEX IF NOT EXISTS idx_config_change_history_config ON public.ak_config_change_history(config_id, created_at DESC); CREATE INDEX IF NOT EXISTS idx_config_change_history_user ON public.ak_config_change_history(changed_by, created_at DESC); CREATE INDEX IF NOT EXISTS idx_config_change_history_type ON public.ak_config_change_history(change_type); COMMENT ON TABLE public.ak_config_change_history IS '配置变更历史记录表'; -- =================================================================== -- 16. 公司信息配置数据初始化 -- =================================================================== -- 插入公司基础信息配置 INSERT INTO public.ak_global_configs (config_key, config_group, config_type, default_value, description, display_order, is_public) VALUES -- 公司基本信息 ('company.name', 'company', 'text', '创新科技有限公司', '公司名称', 1, true), ('company.name_en', 'company', 'text', 'Innovation Technology Co., Ltd.', '公司英文名称', 2, true), ('company.slogan', 'company', 'text', '科技创新,未来可期', '公司口号', 3, true), ('company.description', 'company', 'text', '我们是一家专注于人工智能、大数据、物联网和云计算的创新型科技企业', '公司简介', 4, true), -- 联系信息 ('company.phone', 'contact', 'phone', '400-123-4567', '公司电话', 11, true), ('company.email', 'contact', 'email', 'info@example.com', '公司邮箱', 12, true), ('company.address', 'contact', 'text', '上海市浦东新区张江高科技园区XX路88号', '公司地址', 13, true), ('company.postal_code', 'contact', 'text', '200000', '邮政编码', 14, true), ('company.website', 'contact', 'url', 'https://www.example.com', '公司官网', 15, true), -- 法律信息 ('company.legal_name', 'legal', 'text', '上海创新科技有限公司', '法定名称', 21, true), ('company.registration_number', 'legal', 'text', '91310000123456789X', '工商注册号', 22, false), ('company.icp_license', 'legal', 'text', '沪ICP备12345678号-1', 'ICP备案号', 23, true), ('company.business_license', 'legal', 'text', '统一社会信用代码: 91310000123456789X', '营业执照信息', 24, false), -- 社交媒体 ('company.social.wechat', 'social', 'text', 'innovation_tech_wechat', '微信公众号', 31, true), ('company.social.weibo', 'social', 'url', 'https://weibo.com/innovationtech', '新浪微博', 32, true), ('company.social.linkedin', 'social', 'url', 'https://linkedin.com/company/innovation-tech', 'LinkedIn', 33, true), ('company.social.twitter', 'social', 'url', 'https://twitter.com/innovation_tech', 'Twitter', 34, true), -- 媒体资源 ('company.logo_url', 'media', 'url', '/static/company-logo.png', '公司Logo', 41, true), ('company.logo_dark_url', 'media', 'url', '/static/company-logo-dark.png', '深色Logo', 42, true), ('company.favicon_url', 'media', 'url', '/static/favicon.ico', '网站图标', 43, true), ('company.banner_url', 'media', 'url', '/static/company-banner.jpg', '公司横幅图', 44, true), -- 业务信息 ('company.established_year', 'business', 'number', '2018', '成立年份', 51, true), ('company.employee_count', 'business', 'text', '100+', '员工数量', 52, true), ('company.main_business', 'business', 'text', '人工智能,大数据分析,物联网系统,云计算服务', '主营业务', 53, true), ('company.service_regions', 'business', 'text', '中国大陆,香港,台湾,东南亚', '服务区域', 54, true), -- 技术支持 ('company.support_email', 'support', 'email', 'support@example.com', '技术支持邮箱', 61, true), ('company.support_phone', 'support', 'phone', '400-789-0123', '技术支持电话', 62, true), ('company.support_hours', 'support', 'text', '工作日 9:00-18:00', '支持时间', 63, true), -- AI助手配置 ('ai.assistant_name', 'ai', 'text', 'AI智能助手', 'AI助手名称', 71, true), ('ai.assistant_avatar', 'ai', 'url', '/static/ai-avatar.png', 'AI助手头像', 72, true), ('ai.welcome_message', 'ai', 'text', '您好!我是智能助手,有什么可以帮助您的吗?', 'AI欢迎语', 73, true), ('ai.service_description', 'ai', 'text', '我可以为您提供公司信息查询、业务咨询、技术支持等服务', 'AI服务描述', 74, true) ON CONFLICT (config_key) DO NOTHING; -- 插入多语言翻译数据 INSERT INTO public.ak_global_config_translations (config_id, language_code, translated_value) SELECT gc.id, 'en', CASE gc.config_key -- 公司信息英文翻译 WHEN 'company.slogan' THEN 'Technology Innovation, Promising Future' WHEN 'company.description' THEN 'We are an innovative technology company focused on artificial intelligence, big data, IoT and cloud computing' WHEN 'company.address' THEN 'No.88 XX Road, Zhangjiang High-tech Park, Pudong New Area, Shanghai' WHEN 'company.main_business' THEN 'Artificial Intelligence,Big Data Analytics,IoT Systems,Cloud Computing Services' WHEN 'company.service_regions' THEN 'Mainland China,Hong Kong,Taiwan,Southeast Asia' WHEN 'company.support_hours' THEN 'Weekdays 9:00-18:00' WHEN 'ai.assistant_name' THEN 'AI Assistant' WHEN 'ai.welcome_message' THEN 'Hello! I am your AI assistant. How can I help you today?' WHEN 'ai.service_description' THEN 'I can provide company information, business consultation, technical support and more' ELSE gc.default_value END FROM public.ak_global_configs gc WHERE gc.is_translatable = true ON CONFLICT (config_id, language_code) DO NOTHING; -- 插入日文翻译数据 INSERT INTO public.ak_global_config_translations (config_id, language_code, translated_value) SELECT gc.id, 'ja', CASE gc.config_key WHEN 'company.name' THEN 'イノベーション・テクノロジー株式会社' WHEN 'company.slogan' THEN '技術革新、未来への期待' WHEN 'company.description' THEN '私たちは人工知能、ビッグデータ、IoT、クラウドコンピューティングに特化した革新的な技術企業です' WHEN 'ai.assistant_name' THEN 'AIアシスタント' WHEN 'ai.welcome_message' THEN 'こんにちは!AIアシスタントです。何かお手伝いできることはありますか?' WHEN 'ai.service_description' THEN '会社情報の照会、ビジネス相談、技術サポートなどのサービスを提供できます' ELSE gc.default_value END FROM public.ak_global_configs gc WHERE gc.is_translatable = true ON CONFLICT (config_id, language_code) DO NOTHING; -- =================================================================== -- 17. 配置管理函数 -- =================================================================== -- 获取配置值函数(支持多语言和缓存) CREATE OR REPLACE FUNCTION public.get_config_value( p_config_key VARCHAR(128), p_language_code VARCHAR(10) DEFAULT 'zh-CN', p_fallback_to_default BOOLEAN DEFAULT true ) RETURNS TEXT AS $$ DECLARE config_value TEXT; default_value TEXT; BEGIN -- 首先尝试获取指定语言的翻译值 SELECT gct.translated_value INTO config_value FROM public.ak_global_configs gc JOIN public.ak_global_config_translations gct ON gc.id = gct.config_id WHERE gc.config_key = p_config_key AND gct.language_code = p_language_code AND gc.is_public = true; -- 如果找到翻译值,直接返回 IF config_value IS NOT NULL THEN RETURN config_value; END IF; -- 如果没有找到翻译值且允许回退到默认值 IF p_fallback_to_default THEN SELECT gc.default_value INTO default_value FROM public.ak_global_configs gc WHERE gc.config_key = p_config_key AND gc.is_public = true; RETURN default_value; END IF; -- 都没找到返回NULL RETURN NULL; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION public.get_config_value IS '获取配置值,支持多语言回退'; -- 获取配置分组函数 CREATE OR REPLACE FUNCTION public.get_configs_by_group( p_config_group VARCHAR(64), p_language_code VARCHAR(10) DEFAULT 'zh-CN' ) RETURNS TABLE ( config_key VARCHAR(128), config_value TEXT, config_type VARCHAR(32), display_order INTEGER ) AS $$ BEGIN RETURN QUERY SELECT gc.config_key, COALESCE(gct.translated_value, gc.default_value) as config_value, gc.config_type, gc.display_order FROM public.ak_global_configs gc LEFT JOIN public.ak_global_config_translations gct ON gc.id = gct.config_id AND gct.language_code = p_language_code WHERE gc.config_group = p_config_group AND gc.is_public = true ORDER BY gc.display_order, gc.config_key; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION public.get_configs_by_group IS '获取指定分组的所有配置'; -- 批量获取公司信息函数 CREATE OR REPLACE FUNCTION public.get_company_info( p_language_code VARCHAR(10) DEFAULT 'zh-CN' ) RETURNS JSON AS $$ DECLARE result JSON; BEGIN SELECT json_object_agg( gc.config_key, COALESCE(gct.translated_value, gc.default_value) ) INTO result FROM public.ak_global_configs gc LEFT JOIN public.ak_global_config_translations gct ON gc.id = gct.config_id AND gct.language_code = p_language_code WHERE gc.config_group IN ('company', 'contact', 'legal', 'social', 'media', 'business', 'support', 'ai') AND gc.is_public = true; RETURN result; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION public.get_company_info IS '批量获取公司所有信息,返回JSON格式'; -- 更新配置并记录历史 CREATE OR REPLACE FUNCTION public.update_config_value( p_config_key VARCHAR(128), p_new_value TEXT, p_language_code VARCHAR(10) DEFAULT NULL, -- NULL表示更新默认值 p_changed_by uuid DEFAULT NULL, p_change_reason TEXT DEFAULT NULL ) RETURNS BOOLEAN AS $$ DECLARE config_id_var uuid; old_value_var TEXT; translation_id_var uuid; BEGIN -- 获取配置ID和当前值 SELECT id, default_value INTO config_id_var, old_value_var FROM public.ak_global_configs WHERE config_key = p_config_key; IF config_id_var IS NULL THEN RAISE EXCEPTION '配置项不存在: %', p_config_key; END IF; -- 如果是更新默认值 IF p_language_code IS NULL THEN UPDATE public.ak_global_configs SET default_value = p_new_value, updated_at = now() WHERE id = config_id_var; -- 记录变更历史 INSERT INTO public.ak_config_change_history ( config_id, old_value, new_value, change_type, changed_by, change_reason ) VALUES ( config_id_var, old_value_var, p_new_value, 'update', p_changed_by, p_change_reason ); ELSE -- 更新翻译值 SELECT translated_value INTO old_value_var FROM public.ak_global_config_translations WHERE config_id = config_id_var AND language_code = p_language_code; INSERT INTO public.ak_global_config_translations ( config_id, language_code, translated_value, updated_at ) VALUES ( config_id_var, p_language_code, p_new_value, now() ) ON CONFLICT (config_id, language_code) DO UPDATE SET translated_value = EXCLUDED.translated_value, updated_at = EXCLUDED.updated_at; -- 记录变更历史 INSERT INTO public.ak_config_change_history ( config_id, language_code, old_value, new_value, change_type, changed_by, change_reason ) VALUES ( config_id_var, p_language_code, old_value_var, p_new_value, 'translate', p_changed_by, p_change_reason ); END IF; RETURN true; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION public.update_config_value IS '更新配置值并记录变更历史'; -- 创建视图:完整的多语言配置视图 CREATE OR REPLACE VIEW public.vw_multilingual_configs AS SELECT gc.id, gc.config_key, gc.config_group, gc.config_type, gc.default_value, gc.display_order, gc.is_public, gc.is_translatable, COALESCE( json_object_agg( gct.language_code, gct.translated_value ) FILTER (WHERE gct.language_code IS NOT NULL), '{}'::json ) as translations FROM public.ak_global_configs gc LEFT JOIN public.ak_global_config_translations gct ON gc.id = gct.config_id WHERE gc.is_public = true GROUP BY gc.id, gc.config_key, gc.config_group, gc.config_type, gc.default_value, gc.display_order, gc.is_public, gc.is_translatable ORDER BY gc.config_group, gc.display_order, gc.config_key; COMMENT ON VIEW public.vw_multilingual_configs IS '多语言配置聚合视图'; -- =================================================================== -- 输出创建完成信息 -- =================================================================== DO $$ BEGIN RAISE NOTICE '==================================================================='; RAISE NOTICE '全局配置和公司信息系统创建完成!'; RAISE NOTICE '==================================================================='; RAISE NOTICE '新增表:'; RAISE NOTICE '- ak_global_configs: 全局配置主表'; RAISE NOTICE '- ak_global_config_translations: 配置多语言翻译表'; RAISE NOTICE '- ak_config_change_history: 配置变更历史表'; RAISE NOTICE '==================================================================='; RAISE NOTICE '新增函数:'; RAISE NOTICE '- get_config_value(): 获取配置值(支持多语言)'; RAISE NOTICE '- get_configs_by_group(): 获取分组配置'; RAISE NOTICE '- get_company_info(): 批量获取公司信息'; RAISE NOTICE '- update_config_value(): 更新配置并记录历史'; RAISE NOTICE '==================================================================='; RAISE NOTICE '已初始化公司信息配置:'; RAISE NOTICE '- 公司基本信息(中英日三语)'; RAISE NOTICE '- 联系方式和地址'; RAISE NOTICE '- 法律信息和备案号'; RAISE NOTICE '- 社交媒体链接'; RAISE NOTICE '- AI助手配置'; RAISE NOTICE '==================================================================='; END $$;