二、PostgreSQL 核心表设计
2.1 users — 用户主表
CREATE TABLE users (
user_id VARCHAR(32) PRIMARY KEY,
phone VARCHAR(20) UNIQUE,
email VARCHAR(100),
nickname VARCHAR(30) NOT NULL,
avatar_url TEXT,
mother_tongue VARCHAR(10) NOT NULL,
target_language VARCHAR(10) NOT NULL,
gender VARCHAR(10),
birth_year SMALLINT,
country_code VARCHAR(5) DEFAULT 'SG',
vip_status VARCHAR(20) DEFAULT 'none',
vip_expire_at BIGINT,
coins_balance INTEGER DEFAULT 0,
free_calls_left SMALLINT DEFAULT 10,
free_calls_reset_at BIGINT,
rating_avg DECIMAL(3,1) DEFAULT 5.0,
total_calls INTEGER DEFAULT 0,
trust_score SMALLINT DEFAULT 100,
is_active BOOLEAN DEFAULT TRUE,
is_banned BOOLEAN DEFAULT FALSE,
ban_reason VARCHAR(100),
device_ids JSONB DEFAULT '[]',
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL,
deleted_at BIGINT
);
CREATE INDEX idx_users_lang ON users(mother_tongue, target_language);
CREATE INDEX idx_users_online ON users(is_active) WHERE is_active = TRUE;
CREATE INDEX idx_users_vip ON users(vip_status, vip_expire_at);
2.2 calls — 通话记录
CREATE TABLE calls (
call_id VARCHAR(32) PRIMARY KEY,
caller_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
callee_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
start_time BIGINT NOT NULL,
end_time BIGINT,
duration_seconds INTEGER DEFAULT 0,
caller_language VARCHAR(10),
callee_language VARCHAR(10),
cost_coins INTEGER DEFAULT 0,
earner_coins INTEGER DEFAULT 0,
caller_rating SMALLINT,
callee_rating SMALLINT,
end_reason VARCHAR(20),
has_translation BOOLEAN DEFAULT TRUE,
avg_latency_ms INTEGER,
quality_score DECIMAL(3,1),
created_at BIGINT NOT NULL
) PARTITION BY RANGE (start_time);
CREATE INDEX idx_calls_user_time ON calls(caller_id, start_time DESC);
CREATE INDEX idx_calls_callee_time ON calls(callee_id, start_time DESC);
2.3 translations — 翻译记录
CREATE TABLE translations (
translation_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
call_id VARCHAR(32) NOT NULL REFERENCES calls(call_id),
seq INTEGER NOT NULL,
speaker_id VARCHAR(32) NOT NULL,
source_lang VARCHAR(10) NOT NULL,
target_lang VARCHAR(10) NOT NULL,
source_text TEXT NOT NULL,
translated_text TEXT,
asr_confidence DECIMAL(4,3),
translate_confidence DECIMAL(4,3),
latency_ms INTEGER,
engine VARCHAR(30),
was_cached BOOLEAN DEFAULT FALSE,
is_flagged BOOLEAN DEFAULT FALSE,
created_at BIGINT NOT NULL,
UNIQUE(call_id, seq, speaker_id)
) PARTITION BY RANGE (created_at);
CREATE INDEX idx_translations_call ON translations(call_id, seq);
CREATE INDEX idx_translations_flagged ON translations(is_flagged) WHERE is_flagged = TRUE;
2.4 payments — 支付订单
CREATE TABLE payments (
payment_id VARCHAR(32) PRIMARY KEY,
user_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
type VARCHAR(20) NOT NULL,
amount_sgd DECIMAL(10,2) NOT NULL,
coins_amount INTEGER,
channel VARCHAR(20) NOT NULL,
channel_tx_id VARCHAR(100),
status VARCHAR(20) NOT NULL,
failure_reason VARCHAR(200),
receipt_data TEXT,
metadata JSONB,
created_at BIGINT NOT NULL,
completed_at BIGINT,
CHECK(amount_sgd >= 0)
);
CREATE INDEX idx_payments_user ON payments(user_id, created_at DESC);
CREATE INDEX idx_payments_status ON payments(status, created_at);
2.5 subscriptions — VIP 订阅
CREATE TABLE subscriptions (
subscription_id VARCHAR(32) PRIMARY KEY,
user_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
plan VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL,
start_date BIGINT NOT NULL,
end_date BIGINT NOT NULL,
auto_renew BOOLEAN DEFAULT TRUE,
channel VARCHAR(20),
original_transaction_id VARCHAR(100),
created_at BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
CREATE INDEX idx_subs_user ON subscriptions(user_id, status);
CREATE INDEX idx_subs_expire ON subscriptions(end_date) WHERE status = 'active';
2.6 buddies — 语伴关系
CREATE TABLE buddies (
buddy_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_a_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
user_b_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
status VARCHAR(20) DEFAULT 'active',
is_favorite BOOLEAN DEFAULT FALSE,
total_calls INTEGER DEFAULT 0,
last_call_at BIGINT,
created_at BIGINT NOT NULL,
CHECK(user_a_id < user_b_id),
UNIQUE(user_a_id, user_b_id)
);
CREATE INDEX idx_buddies_user ON buddies(user_a_id, status);
2.7 messages — 私聊消息
CREATE TABLE messages (
message_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
sender_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
receiver_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
type VARCHAR(10) DEFAULT 'text',
content TEXT,
translated_content TEXT,
is_read BOOLEAN DEFAULT FALSE,
is_flagged BOOLEAN DEFAULT FALSE,
created_at BIGINT NOT NULL
) PARTITION BY RANGE (created_at);
CREATE INDEX idx_messages_conv ON messages(sender_id, receiver_id, created_at DESC);
CREATE INDEX idx_messages_unread ON messages(receiver_id, is_read) WHERE is_read = FALSE;
2.8 reports — 举报记录
CREATE TABLE reports (
report_id VARCHAR(32) PRIMARY KEY,
reporter_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
reported_user_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
call_id VARCHAR(32) REFERENCES calls(call_id),
reason VARCHAR(30) NOT NULL,
description TEXT,
severity VARCHAR(10) DEFAULT 'medium',
status VARCHAR(20) DEFAULT 'pending',
resolution VARCHAR(50),
moderator_id VARCHAR(32),
evidence JSONB,
created_at BIGINT NOT NULL,
resolved_at BIGINT
);
CREATE INDEX idx_reports_pending ON reports(status, severity, created_at) WHERE status = 'pending';
CREATE INDEX idx_reports_user ON reports(reported_user_id, status);
2.9 gifts — 礼物记录
CREATE TABLE gifts (
gift_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
from_user_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
to_user_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
gift_type VARCHAR(20) NOT NULL,
coins_cost INTEGER NOT NULL,
call_id VARCHAR(32) REFERENCES calls(call_id),
is_anonymous BOOLEAN DEFAULT FALSE,
created_at BIGINT NOT NULL
);
CREATE INDEX idx_gifts_receiver ON gifts(to_user_id, created_at DESC);
2.10 ad_events — 广告事件
CREATE TABLE ad_events (
event_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id VARCHAR(32) NOT NULL REFERENCES users(user_id),
ad_type VARCHAR(20) NOT NULL,
event_type VARCHAR(20) NOT NULL,
provider VARCHAR(20),
ad_unit_id VARCHAR(50),
reward_granted BOOLEAN DEFAULT FALSE,
reward_coins INTEGER DEFAULT 0,
error_code VARCHAR(20),
created_at BIGINT NOT NULL
) PARTITION BY RANGE (created_at);
CREATE INDEX idx_ads_user_daily ON ad_events(user_id, event_type, created_at DESC);