196 lines
6.6 KiB
Python
196 lines
6.6 KiB
Python
"""Step 1: 全量采集ERP表结构到 erp_schema
|
|
通过 erp-api-gateway 采集985张表的字段信息
|
|
运行: python3 scripts/collect_erp_schema.py
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import json
|
|
import urllib.request
|
|
import urllib.error
|
|
import logging
|
|
import time
|
|
from datetime import datetime
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from app.database import get_session_local
|
|
from sqlalchemy import text
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
logger = logging.getLogger("erp_schema_collect")
|
|
|
|
ERP_API_BASE = "http://127.0.0.1:8300/api/v1"
|
|
ERP_API_KEY = os.getenv("ERP_API_KEY", "erp-gateway-key-bhwl-2026")
|
|
|
|
HEADERS = {
|
|
"X-API-Key": ERP_API_KEY,
|
|
"User-Agent": "CMA-ERP-SCHEMA/1.0",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
|
|
def api_get(path: str) -> dict:
|
|
"""调用ERP API"""
|
|
url = f"{ERP_API_BASE}{path}"
|
|
req = urllib.request.Request(url, headers=HEADERS)
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
return json.loads(resp.read().decode())
|
|
|
|
|
|
def get_table_columns(table_name: str) -> list:
|
|
"""通过 INFORMATION_SCHEMA 查询表字段"""
|
|
sql = f"""
|
|
SELECT
|
|
COLUMN_NAME,
|
|
DATA_TYPE,
|
|
CHARACTER_MAXIMUM_LENGTH,
|
|
IS_NULLABLE,
|
|
COLUMN_DEFAULT
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = '{table_name}'
|
|
ORDER BY ORDINAL_POSITION
|
|
"""
|
|
params = json.dumps({"sql": sql}).encode()
|
|
req = urllib.request.Request(
|
|
f"{ERP_API_BASE}/query",
|
|
data=params,
|
|
headers=HEADERS,
|
|
method="POST"
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
data = json.loads(resp.read().decode())
|
|
return data.get("data", [])
|
|
except Exception as e:
|
|
logger.warning(f" ⚠️ {table_name}: 查询失败 - {e}")
|
|
return []
|
|
|
|
|
|
def get_row_count(table_name: str) -> int:
|
|
"""获取表行数"""
|
|
sql = f"SELECT COUNT(*) as cnt FROM [{table_name}]"
|
|
params = json.dumps({"sql": sql}).encode()
|
|
req = urllib.request.Request(
|
|
f"{ERP_API_BASE}/query",
|
|
data=params,
|
|
headers=HEADERS,
|
|
method="POST"
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
data = json.loads(resp.read().decode())
|
|
rows = data.get("data", [])
|
|
return rows[0]["cnt"] if rows else 0
|
|
except:
|
|
return -1 # 未知
|
|
|
|
|
|
def main():
|
|
db = get_session_local()()
|
|
|
|
try:
|
|
# 1. 获取全部表名
|
|
logger.info("📡 获取ERP全量表名列表...")
|
|
tables_data = api_get("/tables")
|
|
all_tables = tables_data.get("tables", [])
|
|
logger.info(f" 共 {len(all_tables)} 张表")
|
|
|
|
# 2. 获取已采集的表名
|
|
existing = set()
|
|
try:
|
|
rows = db.execute(text("SELECT table_name FROM erp_schema")).fetchall()
|
|
existing = set(row[0] for row in rows)
|
|
except:
|
|
pass
|
|
logger.info(f" 已采集 {len(existing)} 张表,待采集 {len(all_tables) - len(existing)} 张")
|
|
|
|
# 3. 逐表采集
|
|
collected = 0
|
|
skipped = 0
|
|
errors = 0
|
|
|
|
for i, table_name in enumerate(all_tables):
|
|
if table_name in existing:
|
|
skipped += 1
|
|
continue
|
|
|
|
# 进度显示
|
|
if (i + 1) % 50 == 0:
|
|
logger.info(f" 进度: {i+1}/{len(all_tables)} (已采{collected}, 跳过{skipped}, 错误{errors})")
|
|
|
|
# 采集字段
|
|
columns = get_table_columns(table_name)
|
|
if not columns:
|
|
errors += 1
|
|
# 即使查不到字段也记录一个空记录,避免重复查
|
|
collect_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
fields_json = "[]"
|
|
db.execute(
|
|
text("""
|
|
INSERT INTO erp_schema (table_name, total_rows, field_count, fields_json, created_at, updated_at)
|
|
VALUES (:tn, :tr, :fc, :fj, NOW(), NOW())
|
|
ON DUPLICATE KEY UPDATE fields_json=:fj2, total_rows=:tr2, updated_at=NOW()
|
|
"""),
|
|
{"tn": table_name, "tr": -1, "fc": 0, "fj": fields_json, "fj2": fields_json, "tr2": -1}
|
|
)
|
|
db.commit()
|
|
continue
|
|
|
|
# 获取行数
|
|
row_count = get_row_count(table_name)
|
|
field_count = len(columns)
|
|
|
|
fields = []
|
|
for col in columns:
|
|
fields.append({
|
|
"name": col.get("COLUMN_NAME", ""),
|
|
"type": col.get("DATA_TYPE", ""),
|
|
"max_length": col.get("CHARACTER_MAXIMUM_LENGTH"),
|
|
"nullable": col.get("IS_NULLABLE", "YES"),
|
|
"default": col.get("COLUMN_DEFAULT"),
|
|
})
|
|
fields_json = json.dumps(fields, ensure_ascii=False)
|
|
|
|
# 写入 erp_schema
|
|
try:
|
|
collect_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
db.execute(
|
|
text("""
|
|
INSERT INTO erp_schema (table_name, total_rows, field_count, fields_json, created_at, updated_at)
|
|
VALUES (:tn, :tr, :fc, :fj, NOW(), NOW())
|
|
ON DUPLICATE KEY UPDATE total_rows=:tr2, field_count=:fc2, fields_json=:fj2, updated_at=NOW()
|
|
"""),
|
|
{"tn": table_name, "tr": row_count, "fc": field_count, "fj": fields_json,
|
|
"tr2": row_count, "fc2": field_count, "fj2": fields_json}
|
|
)
|
|
db.commit()
|
|
collected += 1
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.warning(f" ⚠️ {table_name}: 写入数据库失败 - {e}")
|
|
errors += 1
|
|
|
|
# 限流:不要打太快
|
|
if collected > 0 and collected % 10 == 0:
|
|
time.sleep(0.5)
|
|
|
|
# 4. 统计
|
|
total = db.execute(text("SELECT COUNT(*) FROM erp_schema")).scalar()
|
|
with_data = db.execute(text("SELECT COUNT(*) FROM erp_schema WHERE field_count > 0")).scalar()
|
|
logger.info(f"\n🎉 采集完成!")
|
|
logger.info(f" 总计: {total} 张表 (erp_schema)")
|
|
logger.info(f" 有字段信息: {with_data} 张")
|
|
logger.info(f" 本次新增: {collected} 张")
|
|
logger.info(f" 跳过(已存在): {skipped} 张")
|
|
logger.info(f" 错误: {errors} 张")
|
|
|
|
except Exception as e:
|
|
logger.error(f"采集失败: {e}", exc_info=True)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|