1730 lines
80 KiB
PL/PgSQL
1730 lines
80 KiB
PL/PgSQL
-- ===================================================================
|
||
-- 多语言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
|
||
$$; |