更新agent列表
This commit is contained in:
-32
@@ -1,32 +0,0 @@
|
||||
# Taiji AI-PAD 环境变量配置
|
||||
|
||||
# 数据库配置
|
||||
ASYNC_DATABASE_URL=postgresql+asyncpg://taiji:By%40123456.@taijipda.postgres.database.azure.com:5432/postgres
|
||||
DATABASE_URL=postgresql://taiji:By%40123456.@taijipda.postgres.database.azure.com:5432/postgres?sslmode=require
|
||||
|
||||
# Redis配置 (暂未启用)
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
# NATS消息队列配置
|
||||
NATS_URL=nats://nats:4222
|
||||
|
||||
# LiteLLM网关配置
|
||||
LITELLM_MASTER_KEY=sk-1234567890abcdef
|
||||
LITELLM_URL=http://litellm-gateway:4000
|
||||
|
||||
# OpenRouter配置
|
||||
OPENROUTER_API_KEY=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
|
||||
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
|
||||
|
||||
# RapidAPI配置
|
||||
RAPIDAPI_KEY=33902cc39dmsha572ec6ae920fb5p13c196jsn8a11209a7e67
|
||||
RAPIDAPI_HOST=rapidapi.com
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET_KEY=your-super-secret-jwt-key-change-this-in-production
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_EXPIRE_MINUTES=1440
|
||||
|
||||
# 应用配置
|
||||
APP_ENV=development
|
||||
LOG_LEVEL=INFO
|
||||
@@ -1,33 +0,0 @@
|
||||
# Taiji AI-PAD 环境变量配置
|
||||
|
||||
# 数据库配置
|
||||
ASYNC_DATABASE_URL=postgresql+asyncpg://taiji:By%40123456.@taijipda.postgres.database.azure.com:5432/postgres
|
||||
DATABASE_URL=postgresql://taiji:By%40123456.@taijipda.postgres.database.azure.com:5432/postgres?sslmode=require
|
||||
|
||||
# Redis配置 (暂未启用)
|
||||
# REDIS_URL已禁用(无本地Redis)
|
||||
REDIS_URL=
|
||||
|
||||
# NATS消息队列配置
|
||||
NATS_URL=nats://nats:4222
|
||||
|
||||
# LiteLLM网关配置
|
||||
LITELLM_MASTER_KEY=sk-1234567890abcdef
|
||||
LITELLM_URL=http://litellm-gateway:4000
|
||||
|
||||
# OpenRouter配置
|
||||
OPENROUTER_API_KEY=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
|
||||
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
|
||||
|
||||
# RapidAPI配置
|
||||
RAPIDAPI_KEY=33902cc39dmsha572ec6ae920fb5p13c196jsn8a11209a7e67
|
||||
RAPIDAPI_HOST=rapidapi.com
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET_KEY=your-super-secret-jwt-key-change-this-in-production
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_EXPIRE_MINUTES=1440
|
||||
|
||||
# 应用配置
|
||||
APP_ENV=development
|
||||
LOG_LEVEL=INFO
|
||||
@@ -1,40 +0,0 @@
|
||||
# taiji-AI-PAD 环境变量配置模板
|
||||
# 复制此文件为 .env 并填写实际的密钥值
|
||||
# cp .env.example .env
|
||||
|
||||
# ========== 数据库配置 ==========
|
||||
POSTGRES_DB=taiji_db
|
||||
POSTGRES_USER=taiji_user
|
||||
POSTGRES_PASSWORD=taiji_pass
|
||||
|
||||
# ========== LiteLLM 网关配置 ==========
|
||||
LITELLM_MASTER_KEY=sk-taiji-master-key
|
||||
|
||||
# ========== OpenRouter 配置 ==========
|
||||
OPENROUTER_API_KEY=your-openrouter-api-key-here
|
||||
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
|
||||
|
||||
# ========== RapidAPI 配置 ==========
|
||||
RAPIDAPI_KEY=your-rapidapi-key-here
|
||||
RAPIDAPI_HOST=rapidapi.com
|
||||
|
||||
# ========== OpenAI 配置(可选)==========
|
||||
# OPENAI_API_KEY=your-openai-api-key-here
|
||||
|
||||
# ========== Anthropic 配置(可选)==========
|
||||
# ANTHROPIC_API_KEY=your-anthropic-api-key-here
|
||||
|
||||
# ========== Langfuse 配置(可选,用于监控)==========
|
||||
# LANGFUSE_PUBLIC_KEY=your-langfuse-public-key
|
||||
# LANGFUSE_SECRET_KEY=your-langfuse-secret-key
|
||||
# LANGFUSE_HOST=https://cloud.langfuse.com
|
||||
|
||||
# ========== 其他服务配置 ==========
|
||||
REDIS_URL=redis://redis:6379
|
||||
NATS_URL=nats://nats:4222
|
||||
DATABASE_URL=postgresql://taiji_user:taiji_pass@postgres:5432/taiji_db
|
||||
|
||||
# ========== AI Agent Manager 配置 ==========
|
||||
# Kubernetes Agent Pod 管理服务
|
||||
AGENT_MANAGER_URL=http://localhost:8000
|
||||
AGENT_K8S_NAMESPACE=ai-agents
|
||||
@@ -20,15 +20,23 @@
|
||||
6. [获取工具列表](#6-获取工具列表)
|
||||
7. [Prometheus Metrics](#7-prometheus-metrics)
|
||||
|
||||
### K8s Agent 管理 API(新增)
|
||||
8. [获取模板列表](#8-获取模板列表)
|
||||
9. [获取模板详情](#9-获取模板详情)
|
||||
10. [创建 K8s Agent](#10-创建-k8s-agent)
|
||||
11. [删除 Agent](#11-删除-agent)
|
||||
12. [获取 Agent 状态](#12-获取-agent-状态)
|
||||
13. [获取 Agent 资源使用](#13-获取-agent-资源使用)
|
||||
|
||||
### MCP 监控 API
|
||||
8. [获取系统性能指标](#8-获取系统性能指标)
|
||||
9. [获取服务统计信息](#9-获取服务统计信息)
|
||||
10. [获取性能趋势数据](#10-获取性能趋势数据)
|
||||
11. [获取系统告警](#11-获取系统告警)
|
||||
12. [获取监控仪表盘聚合](#12-获取监控仪表盘聚合)
|
||||
14. [获取系统性能指标](#14-获取系统性能指标)
|
||||
15. [获取服务统计信息](#15-获取服务统计信息)
|
||||
16. [获取性能趋势数据](#16-获取性能趋势数据)
|
||||
17. [获取系统告警](#17-获取系统告警)
|
||||
18. [获取监控仪表盘聚合](#18-获取监控仪表盘聚合)
|
||||
|
||||
### WebSocket API
|
||||
13. [MCP Protocol WebSocket](#13-mcp-protocol-websocket)
|
||||
19. [MCP Protocol WebSocket](#19-mcp-protocol-websocket)
|
||||
|
||||
---
|
||||
|
||||
@@ -345,11 +353,329 @@ curl -X GET "http://localhost:8002/metrics"
|
||||
|
||||
---
|
||||
|
||||
## K8s Agent 管理 API(新增)
|
||||
|
||||
> **说明**: 这些接口用于管理 Kubernetes 中的 AI Agent Pod,通过 AI Agent Manager 服务实现。
|
||||
|
||||
### 8. 获取模板列表
|
||||
|
||||
**GET** `/agents/templates`
|
||||
|
||||
获取所有可用的 Agent 模板及其所需参数。**此接口无需认证**。
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/agents/templates"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"templates": [
|
||||
{
|
||||
"template": "echo_agent",
|
||||
"port": null,
|
||||
"env_info": {}
|
||||
},
|
||||
{
|
||||
"template": "jina_search_agent",
|
||||
"port": 8080,
|
||||
"env_info": {
|
||||
"required": {
|
||||
"JINA_API_KEY": "Jina API密钥,从 https://jina.ai/ 获取"
|
||||
},
|
||||
"optional": {
|
||||
"SERVICE_PORT": "HTTP服务端口,默认8080",
|
||||
"SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"template": "mysql_agent",
|
||||
"port": null,
|
||||
"env_info": {
|
||||
"required": {
|
||||
"MYSQL_HOST": "MySQL数据库主机地址",
|
||||
"MYSQL_USER": "MySQL用户名",
|
||||
"MYSQL_PASSWORD": "MySQL密码",
|
||||
"MYSQL_DATABASE": "MySQL数据库名",
|
||||
"OPENAI_API_KEY": "OpenAI API密钥"
|
||||
},
|
||||
"optional": {
|
||||
"MYSQL_PORT": "MySQL端口,默认3306"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"count": 7
|
||||
}
|
||||
```
|
||||
|
||||
**可用模板列表**:
|
||||
|
||||
| 模板名称 | 端口 | 说明 |
|
||||
|---------|------|------|
|
||||
| `echo_agent` | - | 简单回显 Agent,用于测试 |
|
||||
| `chat_agent` | - | 聊天对话 Agent |
|
||||
| `code_agent` | - | 代码生成 Agent |
|
||||
| `search_agent` | - | 搜索 Agent |
|
||||
| `jina_search_agent` | 8080 | Jina 网页内容抓取 Agent |
|
||||
| `mysql_agent` | - | MySQL 数据库查询 Agent |
|
||||
| `postgresql_agent` | - | PostgreSQL 数据库查询 Agent |
|
||||
|
||||
---
|
||||
|
||||
### 9. 获取模板详情
|
||||
|
||||
**GET** `/agents/templates/{template_name}`
|
||||
|
||||
获取指定模板的详细信息,包括所需环境变量。**此接口无需认证**。
|
||||
|
||||
**路径参数**:
|
||||
- `template_name` (string, 必填): 模板名称
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/agents/templates/jina_search_agent"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"template": "jina_search_agent",
|
||||
"port": 8080,
|
||||
"env_info": {
|
||||
"required": {
|
||||
"JINA_API_KEY": "Jina API密钥,从 https://jina.ai/ 获取"
|
||||
},
|
||||
"optional": {
|
||||
"SERVICE_PORT": "HTTP服务端口,默认8080",
|
||||
"SERVICE_HOST": "HTTP服务监听地址,默认0.0.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
- `404`: 模板不存在
|
||||
|
||||
---
|
||||
|
||||
### 10. 创建 K8s Agent
|
||||
|
||||
**POST** `/agents`
|
||||
|
||||
创建新的 Agent,如果指定了 `template`,将在 Kubernetes 中创建对应的 Pod。
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"name": "my-jina-agent",
|
||||
"description": "Jina 搜索 Agent",
|
||||
"template": "jina_search_agent",
|
||||
"resource_config": {
|
||||
"cpu_request": "100m",
|
||||
"cpu_limit": "500m",
|
||||
"memory_request": "128Mi",
|
||||
"memory_limit": "512Mi",
|
||||
"replicas": 1,
|
||||
"env": {
|
||||
"JINA_API_KEY": "your-jina-api-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**请求体参数**:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `name` | string | 是 | Agent 名称 |
|
||||
| `description` | string | 否 | Agent 描述 |
|
||||
| `template` | string | 否 | 模板类型(见模板列表) |
|
||||
| `resource_config` | object | 否 | K8s 资源配置 |
|
||||
|
||||
**resource_config 参数**:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `cpu_request` | string | `100m` | CPU 请求量 |
|
||||
| `cpu_limit` | string | `500m` | CPU 限制量 |
|
||||
| `memory_request` | string | `128Mi` | 内存请求量 |
|
||||
| `memory_limit` | string | `512Mi` | 内存限制量 |
|
||||
| `replicas` | integer | 1 | 副本数量 |
|
||||
| `env` | object | `{}` | 环境变量 |
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"id": "d7b0a5c2-5f6a-4c27-9ef9-8d51b94f7a1b",
|
||||
"name": "my-jina-agent",
|
||||
"description": "Jina 搜索 Agent",
|
||||
"template": "jina_search_agent",
|
||||
"pod_name": "my-jina-agent-d7b0a5c2",
|
||||
"pod_ip": null,
|
||||
"k8s_status": "Pending",
|
||||
"service_port": 8080,
|
||||
"access_url": null,
|
||||
"cpu_request": "100m",
|
||||
"cpu_limit": "500m",
|
||||
"memory_request": "128Mi",
|
||||
"memory_limit": "512Mi",
|
||||
"status": "active",
|
||||
"created_at": "2025-12-31T04:38:26Z"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
- `400`: 无效的模板类型
|
||||
- `401`: 未认证
|
||||
- `402`: 账户余额不足
|
||||
- `500`: K8s Pod 创建失败
|
||||
|
||||
---
|
||||
|
||||
### 11. 删除 Agent
|
||||
|
||||
**DELETE** `/agents/{agent_id}`
|
||||
|
||||
删除指定的 Agent。如果 Agent 有关联的 K8s Pod,也会一并删除。
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**路径参数**:
|
||||
- `agent_id` (string, 必填): Agent ID
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8002/agents/d7b0a5c2-5f6a-4c27-9ef9-8d51b94f7a1b" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
**成功响应**:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Agent my-jina-agent 已删除"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
- `401`: 未认证
|
||||
- `403`: 无权限(非 Agent 所有者且非超级管理员)
|
||||
- `404`: Agent 不存在
|
||||
|
||||
---
|
||||
|
||||
### 12. 获取 Agent 状态
|
||||
|
||||
**GET** `/agents/{agent_id}/status`
|
||||
|
||||
获取 Agent 的实时状态。如果 Agent 有关联的 K8s Pod,会从 Agent Manager 获取最新状态。
|
||||
|
||||
**路径参数**:
|
||||
- `agent_id` (string, 必填): Agent ID
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/agents/d7b0a5c2-5f6a-4c27-9ef9-8d51b94f7a1b/status"
|
||||
```
|
||||
|
||||
**响应示例(运行中)**:
|
||||
```json
|
||||
{
|
||||
"id": "d7b0a5c2-5f6a-4c27-9ef9-8d51b94f7a1b",
|
||||
"name": "my-jina-agent",
|
||||
"status": "active",
|
||||
"k8s_status": "Running",
|
||||
"pod_name": "my-jina-agent-d7b0a5c2",
|
||||
"pod_ip": "10.244.1.107",
|
||||
"node": "aks-nodepool1-12345678-vmss000000",
|
||||
"service_port": 8080,
|
||||
"access_url": "http://10.244.1.107:8080",
|
||||
"endpoints": {
|
||||
"root": "http://10.244.1.107:8080/",
|
||||
"health": "http://10.244.1.107:8080/health"
|
||||
},
|
||||
"cpu_request": "100m",
|
||||
"cpu_limit": "500m",
|
||||
"memory_request": "128Mi",
|
||||
"memory_limit": "512Mi",
|
||||
"created_at": "2025-12-31T04:38:26Z",
|
||||
"pod_created_at": "2025-12-31T04:38:26Z",
|
||||
"conditions": [
|
||||
{
|
||||
"type": "Ready",
|
||||
"status": "True",
|
||||
"reason": null
|
||||
},
|
||||
{
|
||||
"type": "ContainersReady",
|
||||
"status": "True",
|
||||
"reason": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**K8s 状态值说明**:
|
||||
|
||||
| 状态 | 说明 |
|
||||
|------|------|
|
||||
| `Pending` | Pod 已被接受,但容器尚未创建 |
|
||||
| `Running` | Pod 已绑定到节点,所有容器已创建 |
|
||||
| `Succeeded` | Pod 中所有容器已成功终止 |
|
||||
| `Failed` | Pod 中所有容器已终止,至少一个容器失败 |
|
||||
| `Unknown` | 无法获取 Pod 状态 |
|
||||
|
||||
---
|
||||
|
||||
### 13. 获取 Agent 资源使用
|
||||
|
||||
**GET** `/agents/{agent_id}/metrics`
|
||||
|
||||
获取 Agent 的 CPU 和内存资源配置信息。
|
||||
|
||||
**路径参数**:
|
||||
- `agent_id` (string, 必填): Agent ID
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8002/agents/d7b0a5c2-5f6a-4c27-9ef9-8d51b94f7a1b/metrics"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"id": "d7b0a5c2-5f6a-4c27-9ef9-8d51b94f7a1b",
|
||||
"name": "my-jina-agent",
|
||||
"requests": {
|
||||
"cpu": "100m",
|
||||
"memory": "128Mi"
|
||||
},
|
||||
"limits": {
|
||||
"cpu": "500m",
|
||||
"memory": "512Mi"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP 监控 API
|
||||
|
||||
**基础URL**: `http://localhost:8002/api/v1/monitoring`
|
||||
|
||||
### 8. 获取系统性能指标
|
||||
### 14. 获取系统性能指标
|
||||
|
||||
**GET** `/api/v1/monitoring/metrics`
|
||||
|
||||
@@ -389,7 +715,7 @@ curl -X GET "http://localhost:8002/api/v1/monitoring/metrics"
|
||||
|
||||
---
|
||||
|
||||
### 9. 获取服务统计信息
|
||||
### 15. 获取服务统计信息
|
||||
|
||||
**GET** `/api/v1/monitoring/stats`
|
||||
|
||||
@@ -427,7 +753,7 @@ curl -X GET "http://localhost:8002/api/v1/monitoring/stats?service=all"
|
||||
|
||||
---
|
||||
|
||||
### 10. 获取性能趋势数据
|
||||
### 16. 获取性能趋势数据
|
||||
|
||||
**GET** `/api/v1/monitoring/trends`
|
||||
|
||||
@@ -462,7 +788,7 @@ curl -X GET "http://localhost:8002/api/v1/monitoring/trends?metric=executions&pe
|
||||
|
||||
---
|
||||
|
||||
### 11. 获取系统告警
|
||||
### 17. 获取系统告警
|
||||
|
||||
**GET** `/api/v1/monitoring/alerts`
|
||||
|
||||
@@ -494,7 +820,7 @@ curl -X GET "http://localhost:8002/api/v1/monitoring/alerts?severity=warning"
|
||||
|
||||
---
|
||||
|
||||
### 12. 获取监控仪表盘聚合
|
||||
### 18. 获取监控仪表盘聚合
|
||||
|
||||
**GET** `/api/v1/monitoring/dashboard`
|
||||
|
||||
@@ -529,7 +855,7 @@ curl -X GET "http://localhost:8002/api/v1/monitoring/dashboard"
|
||||
|
||||
## WebSocket API
|
||||
|
||||
### 13. MCP Protocol WebSocket
|
||||
### 19. MCP Protocol WebSocket
|
||||
|
||||
**WebSocket URL**: `ws://localhost:8002/ws/{agent_name_or_id}`
|
||||
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
将 taiji 数据库的表结构和数据复制到 postgres 数据库
|
||||
|
||||
使用方法:
|
||||
python scripts/copy_database.py
|
||||
|
||||
注意: 需要安装 psycopg2-binary
|
||||
pip install psycopg2-binary
|
||||
"""
|
||||
|
||||
import psycopg2
|
||||
from urllib.parse import quote_plus
|
||||
import sys
|
||||
|
||||
# 数据库连接配置
|
||||
DB_HOST = "taijipda.postgres.database.azure.com"
|
||||
DB_USER = "taiji"
|
||||
DB_PASSWORD = "By@123456."
|
||||
DB_PORT = 5432
|
||||
|
||||
# 源数据库和目标数据库
|
||||
SOURCE_DB = "taiji"
|
||||
TARGET_DB = "postgres"
|
||||
|
||||
|
||||
def get_connection(database):
|
||||
"""获取数据库连接"""
|
||||
return psycopg2.connect(
|
||||
host=DB_HOST,
|
||||
port=DB_PORT,
|
||||
user=DB_USER,
|
||||
password=DB_PASSWORD,
|
||||
database=database,
|
||||
sslmode="require"
|
||||
)
|
||||
|
||||
|
||||
def get_all_tables(conn):
|
||||
"""获取所有用户表"""
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_type = 'BASE TABLE'
|
||||
ORDER BY table_name
|
||||
""")
|
||||
tables = [row[0] for row in cursor.fetchall()]
|
||||
cursor.close()
|
||||
return tables
|
||||
|
||||
|
||||
def get_table_ddl(conn, table_name):
|
||||
"""获取表的 DDL 语句"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 获取列定义
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
character_maximum_length,
|
||||
numeric_precision,
|
||||
numeric_scale,
|
||||
is_nullable,
|
||||
column_default,
|
||||
udt_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = %s
|
||||
ORDER BY ordinal_position
|
||||
""", (table_name,))
|
||||
|
||||
columns = cursor.fetchall()
|
||||
|
||||
if not columns:
|
||||
cursor.close()
|
||||
return None
|
||||
|
||||
# 构建列定义
|
||||
column_defs = []
|
||||
for col in columns:
|
||||
col_name, data_type, char_max_len, num_precision, num_scale, is_nullable, col_default, udt_name = col
|
||||
|
||||
# 处理数据类型
|
||||
if data_type == 'character varying':
|
||||
if char_max_len:
|
||||
type_str = f"VARCHAR({char_max_len})"
|
||||
else:
|
||||
type_str = "VARCHAR"
|
||||
elif data_type == 'character':
|
||||
type_str = f"CHAR({char_max_len})" if char_max_len else "CHAR"
|
||||
elif data_type == 'numeric':
|
||||
if num_precision and num_scale:
|
||||
type_str = f"NUMERIC({num_precision},{num_scale})"
|
||||
elif num_precision:
|
||||
type_str = f"NUMERIC({num_precision})"
|
||||
else:
|
||||
type_str = "NUMERIC"
|
||||
elif data_type == 'ARRAY':
|
||||
type_str = f"{udt_name.lstrip('_')}[]"
|
||||
elif data_type == 'USER-DEFINED':
|
||||
type_str = udt_name
|
||||
else:
|
||||
type_str = data_type.upper()
|
||||
|
||||
# 构建列定义
|
||||
col_def = f' "{col_name}" {type_str}'
|
||||
|
||||
if is_nullable == 'NO':
|
||||
col_def += " NOT NULL"
|
||||
|
||||
if col_default:
|
||||
col_def += f" DEFAULT {col_default}"
|
||||
|
||||
column_defs.append(col_def)
|
||||
|
||||
# 获取主键约束
|
||||
cursor.execute("""
|
||||
SELECT kcu.column_name
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.constraint_type = 'PRIMARY KEY'
|
||||
AND tc.table_schema = 'public'
|
||||
AND tc.table_name = %s
|
||||
ORDER BY kcu.ordinal_position
|
||||
""", (table_name,))
|
||||
|
||||
pk_columns = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
if pk_columns:
|
||||
pk_def = f' PRIMARY KEY ("{"\", \"".join(pk_columns)}")'
|
||||
column_defs.append(pk_def)
|
||||
|
||||
ddl = f'CREATE TABLE IF NOT EXISTS "{table_name}" (\n'
|
||||
ddl += ",\n".join(column_defs)
|
||||
ddl += "\n);"
|
||||
|
||||
cursor.close()
|
||||
return ddl
|
||||
|
||||
|
||||
def get_indexes(conn, table_name):
|
||||
"""获取表的索引"""
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT indexdef
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'public'
|
||||
AND tablename = %s
|
||||
AND indexname NOT LIKE '%%_pkey'
|
||||
""", (table_name,))
|
||||
|
||||
indexes = [row[0] for row in cursor.fetchall()]
|
||||
cursor.close()
|
||||
return indexes
|
||||
|
||||
|
||||
def get_sequences(conn):
|
||||
"""获取所有序列"""
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT sequence_name
|
||||
FROM information_schema.sequences
|
||||
WHERE sequence_schema = 'public'
|
||||
""")
|
||||
sequences = [row[0] for row in cursor.fetchall()]
|
||||
cursor.close()
|
||||
return sequences
|
||||
|
||||
|
||||
def get_sequence_value(conn, sequence_name):
|
||||
"""获取序列当前值"""
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute(f'SELECT last_value FROM "{sequence_name}"')
|
||||
value = cursor.fetchone()[0]
|
||||
except:
|
||||
value = 1
|
||||
cursor.close()
|
||||
return value
|
||||
|
||||
|
||||
def copy_table_data(source_conn, target_conn, table_name):
|
||||
"""复制表数据"""
|
||||
source_cursor = source_conn.cursor()
|
||||
target_cursor = target_conn.cursor()
|
||||
|
||||
# 获取列名
|
||||
source_cursor.execute("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = %s
|
||||
ORDER BY ordinal_position
|
||||
""", (table_name,))
|
||||
|
||||
columns = [row[0] for row in source_cursor.fetchall()]
|
||||
|
||||
if not columns:
|
||||
source_cursor.close()
|
||||
target_cursor.close()
|
||||
return 0
|
||||
|
||||
# 获取数据
|
||||
columns_str = ', '.join([f'"{c}"' for c in columns])
|
||||
source_cursor.execute(f'SELECT {columns_str} FROM "{table_name}"')
|
||||
rows = source_cursor.fetchall()
|
||||
|
||||
if not rows:
|
||||
source_cursor.close()
|
||||
target_cursor.close()
|
||||
return 0
|
||||
|
||||
# 插入数据
|
||||
placeholders = ', '.join(['%s'] * len(columns))
|
||||
insert_sql = f'INSERT INTO "{table_name}" ({columns_str}) VALUES ({placeholders}) ON CONFLICT DO NOTHING'
|
||||
|
||||
for row in rows:
|
||||
try:
|
||||
target_cursor.execute(insert_sql, row)
|
||||
except Exception as e:
|
||||
print(f" 警告: 插入数据失败 - {e}")
|
||||
|
||||
target_conn.commit()
|
||||
|
||||
source_cursor.close()
|
||||
target_cursor.close()
|
||||
|
||||
return len(rows)
|
||||
|
||||
|
||||
def drop_all_tables(conn):
|
||||
"""删除目标数据库中的所有表"""
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 获取所有表
|
||||
cursor.execute("""
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_type = 'BASE TABLE'
|
||||
""")
|
||||
tables = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
if tables:
|
||||
# 禁用外键检查并删除所有表
|
||||
for table in tables:
|
||||
try:
|
||||
cursor.execute(f'DROP TABLE IF EXISTS "{table}" CASCADE')
|
||||
print(f" ✓ 已删除表: {table}")
|
||||
except Exception as e:
|
||||
print(f" ✗ 删除表 {table} 失败: {e}")
|
||||
conn.commit()
|
||||
|
||||
cursor.close()
|
||||
return len(tables)
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("PostgreSQL 数据库复制工具")
|
||||
print(f"源数据库: {SOURCE_DB}")
|
||||
print(f"目标数据库: {TARGET_DB}")
|
||||
print("=" * 60)
|
||||
|
||||
# 连接源数据库
|
||||
print("\n[1] 连接源数据库...")
|
||||
try:
|
||||
source_conn = get_connection(SOURCE_DB)
|
||||
print(f" ✓ 成功连接到 {SOURCE_DB}")
|
||||
except Exception as e:
|
||||
print(f" ✗ 连接失败: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# 连接目标数据库
|
||||
print("\n[2] 连接目标数据库...")
|
||||
try:
|
||||
target_conn = get_connection(TARGET_DB)
|
||||
print(f" ✓ 成功连接到 {TARGET_DB}")
|
||||
except Exception as e:
|
||||
print(f" ✗ 连接失败: {e}")
|
||||
source_conn.close()
|
||||
sys.exit(1)
|
||||
|
||||
# 删除目标数据库中的旧表
|
||||
print("\n[3] 清理目标数据库旧表...")
|
||||
dropped_count = drop_all_tables(target_conn)
|
||||
print(f" 共删除 {dropped_count} 个旧表")
|
||||
|
||||
# 获取所有表
|
||||
print("\n[4] 获取源数据库表列表...")
|
||||
tables = get_all_tables(source_conn)
|
||||
print(f" 找到 {len(tables)} 个表:")
|
||||
for t in tables:
|
||||
print(f" - {t}")
|
||||
|
||||
# 复制表结构
|
||||
print("\n[5] 复制表结构...")
|
||||
target_cursor = target_conn.cursor()
|
||||
|
||||
for table in tables:
|
||||
print(f" 处理表: {table}")
|
||||
|
||||
# 获取并执行 DDL
|
||||
ddl = get_table_ddl(source_conn, table)
|
||||
if ddl:
|
||||
try:
|
||||
target_cursor.execute(ddl)
|
||||
target_conn.commit()
|
||||
print(f" ✓ 表结构已创建")
|
||||
except Exception as e:
|
||||
target_conn.rollback()
|
||||
if "already exists" in str(e):
|
||||
print(f" ○ 表已存在,跳过创建")
|
||||
else:
|
||||
print(f" ✗ 创建失败: {e}")
|
||||
|
||||
# 获取并创建索引
|
||||
indexes = get_indexes(source_conn, table)
|
||||
for idx in indexes:
|
||||
try:
|
||||
# 修改索引名以避免冲突
|
||||
target_cursor.execute(idx)
|
||||
target_conn.commit()
|
||||
print(f" ✓ 索引已创建")
|
||||
except Exception as e:
|
||||
target_conn.rollback()
|
||||
if "already exists" in str(e):
|
||||
print(f" ○ 索引已存在,跳过")
|
||||
else:
|
||||
print(f" ✗ 索引创建失败: {e}")
|
||||
|
||||
target_cursor.close()
|
||||
|
||||
# 复制数据
|
||||
print("\n[6] 复制表数据...")
|
||||
for table in tables:
|
||||
print(f" 复制表: {table}")
|
||||
try:
|
||||
count = copy_table_data(source_conn, target_conn, table)
|
||||
print(f" ✓ 已复制 {count} 行数据")
|
||||
except Exception as e:
|
||||
print(f" ✗ 复制失败: {e}")
|
||||
|
||||
# 更新序列
|
||||
print("\n[7] 同步序列值...")
|
||||
sequences = get_sequences(source_conn)
|
||||
target_cursor = target_conn.cursor()
|
||||
|
||||
for seq in sequences:
|
||||
try:
|
||||
value = get_sequence_value(source_conn, seq)
|
||||
target_cursor.execute(f'SELECT setval(\'{seq}\', {value}, true)')
|
||||
target_conn.commit()
|
||||
print(f" ✓ 序列 {seq} 设置为 {value}")
|
||||
except Exception as e:
|
||||
target_conn.rollback()
|
||||
print(f" ✗ 序列 {seq} 同步失败: {e}")
|
||||
|
||||
target_cursor.close()
|
||||
|
||||
# 关闭连接
|
||||
source_conn.close()
|
||||
target_conn.close()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("数据库复制完成!")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user