feat: add database migrations for mid-platform tables [AC-IDMP-SHARE, AC-MARH-01~12]

- Add mid-platform tables migration (005_create_mid_tables.sql)
- Add shared_sessions table migration (006_create_shared_sessions.sql)
- Add migration helper scripts
This commit is contained in:
MerCry 2026-03-05 18:14:48 +08:00
parent 9e77923d3a
commit c7c94e8cd9
4 changed files with 233 additions and 0 deletions

View File

@ -0,0 +1,67 @@
-- [AC-IDMP-05/07/09/20] 中台改造数据库迁移
-- 创建高风险策略表、会话模式记录表、审计日志表
-- 高风险策略配置表
CREATE TABLE IF NOT EXISTS high_risk_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id VARCHAR(255) NOT NULL,
scenario VARCHAR(64) NOT NULL,
handler_mode VARCHAR(32) NOT NULL DEFAULT 'micro_flow',
flow_id UUID REFERENCES script_flows(id),
transfer_message TEXT,
keywords JSONB,
patterns JSONB,
priority INTEGER DEFAULT 0,
is_enabled BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_high_risk_policies_tenant ON high_risk_policies(tenant_id);
CREATE INDEX IF NOT EXISTS ix_high_risk_policies_tenant_enabled ON high_risk_policies(tenant_id, is_enabled);
-- 会话模式记录表
CREATE TABLE IF NOT EXISTS session_mode_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id VARCHAR(255) NOT NULL,
session_id VARCHAR(255) NOT NULL,
mode VARCHAR(32) NOT NULL DEFAULT 'BOT_ACTIVE',
reason TEXT,
switched_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT ix_session_mode_records_tenant_session UNIQUE (tenant_id, session_id)
);
-- 中台审计日志表
CREATE TABLE IF NOT EXISTS mid_audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id VARCHAR(255) NOT NULL,
session_id VARCHAR(255) NOT NULL,
request_id VARCHAR(255) NOT NULL,
generation_id VARCHAR(255) NOT NULL,
mode VARCHAR(32) NOT NULL,
intent VARCHAR(255),
tool_calls JSONB,
guardrail_triggered BOOLEAN DEFAULT FALSE,
fallback_reason_code VARCHAR(64),
react_iterations INTEGER,
high_risk_scenario VARCHAR(64),
latency_ms INTEGER,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_mid_audit_logs_tenant_session ON mid_audit_logs(tenant_id, session_id);
CREATE INDEX IF NOT EXISTS ix_mid_audit_logs_tenant_request ON mid_audit_logs(tenant_id, request_id);
CREATE INDEX IF NOT EXISTS ix_mid_audit_logs_tenant_generation ON mid_audit_logs(tenant_id, generation_id);
CREATE INDEX IF NOT EXISTS ix_mid_audit_logs_created ON mid_audit_logs(created_at);
-- 插入默认高风险策略(空集保护)
-- 注意:这里只插入示例,实际由租户配置
INSERT INTO high_risk_policies (tenant_id, scenario, handler_mode, keywords, priority, is_enabled)
VALUES
('default', 'refund', 'micro_flow', '["退款", "退货", "退钱", "退费"]'::jsonb, 100, TRUE),
('default', 'complaint_escalation', 'micro_flow', '["投诉", "举报", "差评", "曝光"]'::jsonb, 100, TRUE),
('default', 'privacy_sensitive_promise', 'micro_flow', '["承诺", "保证", "隐私", "个人信息"]'::jsonb, 100, TRUE),
('default', 'transfer', 'transfer', '["人工", "转人工", "人工客服", "真人"]'::jsonb, 100, TRUE)
ON CONFLICT DO NOTHING;

View File

@ -0,0 +1,24 @@
-- [AC-IDMP-SHARE] 对话分享功能数据库迁移
-- 创建 shared_sessions 表,支持通过链接分享对话
-- 分享会话表
CREATE TABLE IF NOT EXISTS shared_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
share_token VARCHAR(255) NOT NULL UNIQUE,
session_id VARCHAR(255) NOT NULL,
tenant_id VARCHAR(255) NOT NULL,
title VARCHAR(255),
description TEXT,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
max_concurrent_users INTEGER DEFAULT 10,
current_users INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_share_token ON shared_sessions(share_token);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_session_id ON shared_sessions(session_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_tenant ON shared_sessions(tenant_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_tenant_session ON shared_sessions(tenant_id, session_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_expires_at ON shared_sessions(expires_at);

View File

@ -0,0 +1,71 @@
"""
Migration script to create shared_sessions table.
Run: python scripts/migrations/create_shared_sessions_table.py
"""
import asyncio
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.config import get_settings
async def run_migration():
"""Run the migration to create shared_sessions table."""
settings = get_settings()
engine = create_async_engine(settings.database_url, echo=True)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
migration_sql = """
CREATE TABLE IF NOT EXISTS shared_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
share_token VARCHAR(255) NOT NULL UNIQUE,
session_id VARCHAR(255) NOT NULL,
tenant_id VARCHAR(255) NOT NULL,
title VARCHAR(255),
description TEXT,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
max_concurrent_users INTEGER DEFAULT 10,
current_users INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_share_token ON shared_sessions(share_token);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_session_id ON shared_sessions(session_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_tenant ON shared_sessions(tenant_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_tenant_session ON shared_sessions(tenant_id, session_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_expires_at ON shared_sessions(expires_at);
"""
async with async_session_maker() as session:
for statement in migration_sql.strip().split(';'):
statement = statement.strip()
if statement and not statement.startswith('--'):
try:
await session.execute(text(statement))
print(f"Executed: {statement[:60]}...")
except Exception as e:
error_str = str(e).lower()
if "already exists" in error_str or "duplicate" in error_str:
print(f"Skipped (already exists): {statement[:60]}...")
else:
print(f"Error: {e}")
raise
await session.commit()
print("\nMigration completed successfully!")
await engine.dispose()
if __name__ == "__main__":
asyncio.run(run_migration())

View File

@ -0,0 +1,71 @@
"""
Migration script to create shared_sessions table.
Run: python scripts/migrations/run_shared_sessions_migration.py
"""
import asyncio
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker
from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.config import get_settings
async def run_migration():
"""Run the migration to create shared_sessions table."""
settings = get_settings()
engine = create_async_engine(settings.database_url, echo=True)
async_session_maker = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
migration_sql = """
CREATE TABLE IF NOT EXISTS shared_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
share_token VARCHAR(255) NOT NULL UNIQUE,
session_id VARCHAR(255) NOT NULL,
tenant_id VARCHAR(255) NOT NULL,
title VARCHAR(255),
description TEXT,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
max_concurrent_users INTEGER DEFAULT 10,
current_users INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_share_token ON shared_sessions(share_token);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_session_id ON shared_sessions(session_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_tenant ON shared_sessions(tenant_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_tenant_session ON shared_sessions(tenant_id, session_id);
CREATE INDEX IF NOT EXISTS ix_shared_sessions_expires_at ON shared_sessions(expires_at);
"""
async with async_session_maker() as session:
for statement in migration_sql.strip().split(';'):
statement = statement.strip()
if statement and not statement.startswith('--'):
try:
await session.execute(text(statement))
print(f"Executed: {statement[:60]}...")
except Exception as e:
error_str = str(e).lower()
if "already exists" in error_str or "duplicate" in error_str:
print(f"Skipped (already exists): {statement[:60]}...")
else:
print(f"Error: {e}")
raise
await session.commit()
print("\nMigration completed successfully!")
await engine.dispose()
if __name__ == "__main__":
asyncio.run(run_migration())