提交 122bc765 authored 作者: 陈泽健's avatar 陈泽健

feat(deploy,skills): 达梦数据库导入脚本升级支持多库导入,新增 DM8-PROCESS 技能代码

- 自动化部署脚本(arm/x86): import_dm_dmp.sh 升级支持 DM_DEVOPS/DM_VOICE/DM_OFFLINE/DM_HUAZHAO2 多库导入,统一密码为 dNrprU&2S!
- 自动化部署脚本(arm/x86): auto_deploy_dm8.sh 导入逻辑从 DDL SQL 切换为 dmp 文件模式
- 新增 .claude/skills/DM8-PROCESS/code/ 13 个 Python 脚本(达梦数据库完整核对/修复/导出工具集)
- 新增 .claude/skills/HANDOFF/ 交接文档技能
- 新增 arm 架构 FastDFS 启动脚本 (fastdfs_start.sh)
- Docs: 更新 DM DEVOPS 库核对计划执行文档,新增 V2 核对/修复/验证完成项
- 调整 CreateCMD 脚本格式 (LF→CRLF)
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 1fc095d1
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
深入分析 DM_DEVOPS 剩余的索引和数据差异问题
1. 分析 7 个缺失索引的实际字段情况(字段名大小写)
2. 分析 3 张数据差异表的字段类型问题
"""
import pymysql
import dmPython
# 配置
MYSQL_HOST = '192.168.5.41'
MYSQL_PORT = 8306
MYSQL_USER = 'root'
MYSQL_PWD = 'dNrprU&2S'
MYSQL_DB = 'devops'
DM_HOST = '192.168.5.40'
DM_PORT = 5236
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_SCHEMA = 'DM_DEVOPS'
def main():
mysql_conn = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
password=MYSQL_PWD, database=MYSQL_DB,
charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)
dm_conn = dmPython.connect(user=DM_USER, password=DM_PWD, server=DM_HOST, port=DM_PORT)
print("="*60)
print("分析 7 个缺失索引的字段情况")
print("="*60)
# 7 个缺失索引对应的表和字段(MySQL 端)
missing_idx_tables = {
'auth_group': ['name'],
'auth_user': ['username'],
'cmdb_roomcall': ['recoveruser_id'],
'cmdb_roomdevice': ['compnay_id'],
'cmdb_roommessage': ['compnay_id'],
'cmdb_roomuser_copy': ['authuser_id', 'company_id'],
'cmdb_serverlog_copy1': ['company_id', 'user_id', 'master_id'],
}
for table, fields in missing_idx_tables.items():
print(f"\n--- {table} ---")
# 查 MySQL 字段
cur = mysql_conn.cursor()
cur.execute(f"SHOW COLUMNS FROM `{table}`")
mysql_cols = {row['Field']: row['Type'] for row in cur.fetchall()}
cur.close()
# 查达梦字段
cur = dm_conn.cursor()
cur.execute(f"""
SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH
FROM DBA_TAB_COLUMNS
WHERE OWNER='{DM_SCHEMA}' AND TABLE_NAME='{table}'
""")
dm_cols = {row[0]: f"{row[1]}({row[2]})" for row in cur.fetchall()}
cur.close()
print(f"MySQL 字段: {list(mysql_cols.keys())}")
print(f"达梦字段: {list(dm_cols.keys())}")
for field in fields:
# MySQL 端实际字段名
mysql_match = [c for c in mysql_cols.keys() if c.lower() == field.lower()]
# 达梦端实际字段名
dm_match = [c for c in dm_cols.keys() if c.lower() == field.lower()]
print(f"\n 字段 '{field}':")
print(f" MySQL 实际: {mysql_match} -> 类型={mysql_cols.get(mysql_match[0]) if mysql_match else 'NOT FOUND'}")
print(f" 达梦 实际: {dm_match} -> 类型={dm_cols.get(dm_match[0]) if dm_match else 'NOT FOUND'}")
# 分析 3 张数据差异表
print("\n\n" + "="*60)
print("分析 3 张数据差异表的字段类型")
print("="*60)
diff_tables = ['cmdb_roommonitor', 'uplayer_devicegroup', 'uplayer_devicemaster']
for table in diff_tables:
print(f"\n--- {table} ---")
# 查 MySQL 字段类型
cur = mysql_conn.cursor()
cur.execute(f"SHOW COLUMNS FROM `{table}`")
mysql_cols = {row['Field']: row['Type'] for row in cur.fetchall()}
cur.close()
# 查达梦字段类型
cur = dm_conn.cursor()
cur.execute(f"""
SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE
FROM DBA_TAB_COLUMNS
WHERE OWNER='{DM_SCHEMA}' AND TABLE_NAME='{table}'
ORDER BY COLUMN_ID
""")
dm_cols = {}
for row in cur.fetchall():
name = row[0]
dtype = row[1]
dlen = row[2]
dprec = row[3]
dscale = row[4]
if dprec is not None and dprec > 0:
if dscale is not None and dscale > 0:
type_str = f"{dtype}({dprec},{dscale})"
else:
type_str = f"{dtype}({dprec})"
elif dlen is not None and dlen > 0:
type_str = f"{dtype}({dlen})"
else:
type_str = dtype
dm_cols[name] = type_str
cur.close()
# 对比每个字段
print(f"\n字段类型对比:")
for col_name, mysql_type in mysql_cols.items():
dm_col_name = None
for dc in dm_cols.keys():
if dc.lower() == col_name.lower():
dm_col_name = dc
break
if dm_col_name:
dm_type = dm_cols[dm_col_name]
mismatch = "✗" if mysql_type.upper() != dm_type.upper() else "✓"
print(f" {mismatch} {col_name}: MySQL={mysql_type}, 达梦={dm_type}")
else:
print(f" ✗ {col_name}: MySQL={mysql_type}, 达梦=缺失")
mysql_conn.close()
dm_conn.close()
if __name__ == '__main__':
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DM_DEVOPS 库完整核对脚本
核对 MySQL devops 库和达梦 DM_DEVOPS 库的:
1. 表结构
2. 字段(列名、类型、长度、默认值)
3. 表数据
4. 表索引
5. 字段默认值
"""
import pymysql
import dmPython
import json
from collections import defaultdict
# ==================== 配置信息 ====================
# MySQL 配置
MYSQL_HOST = '192.168.5.41'
MYSQL_PORT = 8306
MYSQL_USER = 'root'
MYSQL_PWD = 'dNrprU&2S'
MYSQL_DB = 'devops'
# 达梦配置
DM_HOST = '192.168.5.40'
DM_PORT = 5236
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_SCHEMA = 'DM_DEVOPS'
# 输出文件
OUTPUT_FILE = 'Docs/devops_complete_check.txt'
# 不迁移的表(历史数据、临时数据)
SKIP_TABLES = [
'cmdb_servermonitor_date', # 服务器监控日期关联表(历史数据)
'cmdb_datemonitor', # 日期监控数据(历史数据)
'cmdb_servermonitor', # 服务器监控配置(可重新配置)
'uvoice_recordtime', # 录音时间记录(历史数据)
'django_session', # Django会话临时数据
]
# ==================== 连接数据库 ====================
def connect_mysql():
"""连接 MySQL"""
try:
conn = pymysql.connect(
host=MYSQL_HOST,
port=MYSQL_PORT,
user=MYSQL_USER,
password=MYSQL_PWD,
database=MYSQL_DB,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
print(f"[OK] MySQL 连接成功: {MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}")
return conn
except Exception as e:
print(f"[ERROR] MySQL 连接失败: {e}")
return None
def connect_dm():
"""连接达梦"""
try:
conn = dmPython.connect(
user=DM_USER,
password=DM_PWD,
server=DM_HOST,
port=DM_PORT
)
print(f"[OK] 达梦连接成功: {DM_HOST}:{DM_PORT}/{DM_SCHEMA}")
return conn
except Exception as e:
print(f"[ERROR] 达梦连接失败: {e}")
return None
# ==================== 获取表列表 ====================
def get_mysql_tables(conn):
"""获取 MySQL 所有表名"""
cursor = conn.cursor()
cursor.execute("SHOW TABLES")
tables = [row[f'Tables_in_{MYSQL_DB}'] for row in cursor.fetchall()]
cursor.close()
return sorted(tables)
def get_dm_tables(conn):
"""获取达梦 DM_DEVOPS 所有表名"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT TABLE_NAME
FROM DBA_TABLES
WHERE OWNER = '{DM_SCHEMA}'
ORDER BY TABLE_NAME
""")
# 达梦表名可能是小写,保留原始大小写
tables = [row[0] for row in cursor.fetchall()]
cursor.close()
return sorted(tables)
# ==================== 获取表结构 ====================
def get_mysql_columns(conn, table):
"""获取 MySQL 表的所有列信息"""
cursor = conn.cursor()
cursor.execute(f"SHOW FULL COLUMNS FROM {table}")
columns = cursor.fetchall()
cursor.close()
return columns
def get_dm_columns(conn, table):
"""获取达梦表的所有列信息"""
cursor = conn.cursor()
# 达梦表名可能是小写,需要用双引号包裹
cursor.execute(f"""
SELECT
COLUMN_NAME,
DATA_TYPE,
DATA_LENGTH,
DATA_PRECISION,
DATA_SCALE,
NULLABLE,
DATA_DEFAULT
FROM DBA_TAB_COLUMNS
WHERE OWNER = '{DM_SCHEMA}'
AND TABLE_NAME = '{table}'
ORDER BY COLUMN_ID
""")
rows = cursor.fetchall()
columns = []
for row in rows:
col_name = row[0]
col_type = row[1]
# 构建完整类型
if row[3] and row[4]: # DATA_PRECISION 和 DATA_SCALE 存在
col_type_full = f"{col_type}({row[3]},{row[4]})"
elif row[2]: # DATA_LENGTH 存在
col_type_full = f"{col_type}({row[2]})"
else:
col_type_full = col_type
columns.append({
'COLUMN_NAME': col_name,
'DATA_TYPE': col_type_full,
'NULLABLE': row[5],
'DATA_DEFAULT': row[6] if row[6] else None
})
cursor.close()
return columns
# ==================== 获取索引信息 ====================
def get_mysql_indexes(conn, table):
"""获取 MySQL 表的索引信息"""
cursor = conn.cursor()
cursor.execute(f"SHOW INDEX FROM {table}")
indexes = cursor.fetchall()
cursor.close()
# 按索引名分组
idx_dict = defaultdict(list)
for row in indexes:
idx_name = row['Key_name']
idx_dict[idx_name].append({
'COLUMN_NAME': row['Column_name'],
'NON_UNIQUE': row['Non_unique'],
'SEQ_IN_INDEX': row['Seq_in_index']
})
return idx_dict
def get_dm_indexes(conn, table):
"""获取达梦表的索引信息"""
cursor = conn.cursor()
# 达梦表名可能是小写,需要用双引号包裹
# 达梦需要从 DBA_INDEXES 和 DBA_IND_COLUMNS 两个表联合查询
cursor.execute(f"""
SELECT
i.INDEX_NAME,
ic.COLUMN_NAME,
i.UNIQUENESS
FROM DBA_INDEXES i
JOIN DBA_IND_COLUMNS ic ON i.INDEX_NAME = ic.INDEX_NAME AND i.OWNER = ic.INDEX_OWNER
WHERE i.TABLE_OWNER = '{DM_SCHEMA}'
AND i.TABLE_NAME = '{table}'
ORDER BY i.INDEX_NAME, ic.COLUMN_POSITION
""")
rows = cursor.fetchall()
cursor.close()
# 按索引名分组
idx_dict = defaultdict(list)
for row in rows:
idx_name = row[0]
idx_dict[idx_name].append({
'COLUMN_NAME': row[1],
'UNIQUENESS': row[2]
})
return idx_dict
# ==================== 获取数据行数 ====================
def get_mysql_count(conn, table):
"""获取 MySQL 表行数"""
cursor = conn.cursor()
cursor.execute(f"SELECT COUNT(*) as cnt FROM {table}")
count = cursor.fetchone()['cnt']
cursor.close()
return count
def get_dm_count(conn, table):
"""获取达梦表行数"""
cursor = conn.cursor()
# 达梦表名可能是小写,需要用双引号包裹
cursor.execute(f"SELECT COUNT(*) FROM \"{DM_SCHEMA}\".\"{table}\"")
count = cursor.fetchone()[0]
cursor.close()
return count
# ==================== 对比逻辑 ====================
def compare_tables(mysql_tables, dm_tables):
"""对比表列表"""
# MySQL 表名转大写
mysql_set = set([t.upper() for t in mysql_tables])
# 达梦表名转大写
dm_set = set([t.upper() for t in dm_tables])
missing_in_dm = mysql_set - dm_set
extra_in_dm = dm_set - mysql_set
return missing_in_dm, extra_in_dm
def compare_columns(mysql_cols, dm_cols):
"""对比列结构"""
mysql_col_names = set([col['Field'].upper() for col in mysql_cols])
dm_col_names = set([col['COLUMN_NAME'].upper() for col in dm_cols])
missing_cols = mysql_col_names - dm_col_names
extra_cols = dm_col_names - mysql_col_names
# 对比默认值
missing_defaults = []
for mysql_col in mysql_cols:
col_name = mysql_col['Field'].upper()
mysql_default = mysql_col['Default']
# 找达梦对应的列
dm_col = None
for col in dm_cols:
if col['COLUMN_NAME'].upper() == col_name:
dm_col = col
break
if dm_col and mysql_default is not None:
# MySQL有默认值,检查达梦是否有
dm_default = dm_col['DATA_DEFAULT']
if dm_default is None:
missing_defaults.append({
'COLUMN': col_name,
'MYSQL_DEFAULT': mysql_default,
'DM_DEFAULT': None
})
return missing_cols, extra_cols, missing_defaults
def compare_indexes(mysql_indexes, dm_indexes):
"""对比索引"""
mysql_idx_names = set(mysql_indexes.keys())
dm_idx_names = set([k.upper() for k in dm_indexes.keys()])
# 主键索引不对比(PRIMARY)
mysql_idx_names.discard('PRIMARY')
missing_indexes = mysql_idx_names - dm_idx_names
extra_indexes = dm_idx_names - mysql_idx_names
return missing_indexes, extra_indexes
# ==================== 主核对函数 ====================
def complete_check():
"""完整核对"""
print("\n" + "="*60)
print("DM_DEVOPS 库完整核对")
print("="*60 + "\n")
# 连接数据库
mysql_conn = connect_mysql()
dm_conn = connect_dm()
if not mysql_conn or not dm_conn:
return
# 获取表列表
mysql_tables = get_mysql_tables(mysql_conn)
dm_tables = get_dm_tables(dm_conn)
# 创建达梦表名映射(大写 -> 实际表名)
dm_table_map = {t.upper(): t for t in dm_tables}
print(f"\nMySQL 表数量: {len(mysql_tables)}")
print(f"达梦 表数量: {len(dm_tables)}\n")
# 对比表
missing_tables, extra_tables = compare_tables(mysql_tables, dm_tables)
results = {
'missing_tables': sorted(list(missing_tables)),
'extra_tables': sorted(list(extra_tables)),
'missing_columns': defaultdict(list),
'missing_defaults': defaultdict(list),
'missing_indexes': defaultdict(list),
'data_diff': []
}
# 逐表对比
print("开始逐表对比...")
for table in mysql_tables:
if table.lower() in SKIP_TABLES:
print(f" [跳过] {table} (不迁移的表)")
continue
table_upper = table.upper()
# 检查表是否存在
if table_upper in missing_tables:
print(f" [缺失表] {table}")
continue
# 获取达梦实际表名
dm_table_name = dm_table_map.get(table_upper, table)
# 获取列信息
mysql_cols = get_mysql_columns(mysql_conn, table)
dm_cols = get_dm_columns(dm_conn, dm_table_name)
# 对比列
missing_cols, extra_cols, missing_defaults = compare_columns(mysql_cols, dm_cols)
if missing_cols:
results['missing_columns'][table_upper] = sorted(list(missing_cols))
print(f" [缺失列] {table}: {sorted(list(missing_cols))}")
if missing_defaults:
results['missing_defaults'][table_upper] = missing_defaults
print(f" [缺失默认值] {table}: {len(missing_defaults)} 个")
# 获取索引信息
mysql_indexes = get_mysql_indexes(mysql_conn, table)
dm_indexes = get_dm_indexes(dm_conn, dm_table_name)
# 对比索引
missing_idx, extra_idx = compare_indexes(mysql_indexes, dm_indexes)
if missing_idx:
results['missing_indexes'][table_upper] = sorted(list(missing_idx))
print(f" [缺失索引] {table}: {sorted(list(missing_idx))}")
# 对比数据行数
mysql_count = get_mysql_count(mysql_conn, table)
dm_count = get_dm_count(dm_conn, dm_table_name)
if mysql_count != dm_count:
results['data_diff'].append({
'TABLE': table_upper,
'MYSQL_COUNT': mysql_count,
'DM_COUNT': dm_count,
'DIFF': mysql_count - dm_count
})
print(f" [数据差异] {table}: MySQL {mysql_count} 行, 达梦 {dm_count} 行")
# 输出结果
print("\n" + "="*60)
print("核对完成,输出结果到文件")
print("="*60 + "\n")
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
f.write("# DM_DEVOPS 库完整核对结果\n\n")
f.write(f"核对时间: {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write("## 一、表差异\n\n")
f.write(f"### 1.1 达梦缺失的表 ({len(results['missing_tables'])} 张)\n\n")
if results['missing_tables']:
for table in results['missing_tables']:
f.write(f"- {table}\n")
else:
f.write("[OK] 无缺失表\n")
f.write(f"\n### 1.2 达梦多余的表 ({len(results['extra_tables'])} 张)\n\n")
if results['extra_tables']:
for table in results['extra_tables']:
f.write(f"- {table}\n")
else:
f.write("[OK] 无多余表\n")
f.write("\n## 二、列差异\n\n")
f.write(f"### 2.1 缺失的列 ({len(results['missing_columns'])} 张表)\n\n")
if results['missing_columns']:
for table, cols in results['missing_columns'].items():
f.write(f"- **{table}**: {', '.join(cols)}\n")
else:
f.write("[OK] 无缺失列\n")
f.write("\n## 三、默认值差异\n\n")
f.write(f"### 3.1 缺失的默认值 ({len(results['missing_defaults'])} 张表)\n\n")
if results['missing_defaults']:
for table, defaults in results['missing_defaults'].items():
f.write(f"- **{table}**:\n")
for d in defaults:
f.write(f" - {d['COLUMN']}: MySQL默认值 `{d['MYSQL_DEFAULT']}`, 达梦无默认值\n")
else:
f.write("[OK] 无缺失默认值\n")
f.write("\n## 四、索引差异\n\n")
f.write(f"### 4.1 缺失的索引 ({len(results['missing_indexes'])} 张表)\n\n")
if results['missing_indexes']:
for table, indexes in results['missing_indexes'].items():
f.write(f"- **{table}**: {', '.join(indexes)}\n")
else:
f.write("[OK] 无缺失索引\n")
f.write("\n## 五、数据差异\n\n")
f.write(f"### 5.1 数据行数不一致 ({len(results['data_diff'])} 张表)\n\n")
if results['data_diff']:
# 按差异降序排序
sorted_diff = sorted(results['data_diff'], key=lambda x: abs(x['DIFF']), reverse=True)
for d in sorted_diff:
status = "[WARN]" if d['MYSQL_COUNT'] > d['DM_COUNT'] else "[OK]"
f.write(f"- {status} **{d['TABLE']}**: MySQL {d['MYSQL_COUNT']} 行, 达梦 {d['DM_COUNT']} 行, 差异 {d['DIFF']} 行\n")
else:
f.write("[OK] 数据行数一致\n")
f.write("\n## 六、不迁移的表\n\n")
for table in SKIP_TABLES:
f.write(f"- {table}\n")
f.write("\n## 七、修复建议\n\n")
f.write("### 7.1 优先级排序\n\n")
f.write("1. **P0 - 数据缺失**: 补充关键业务数据\n")
f.write("2. **P1 - 缺失表**: 创建缺失的表结构\n")
f.write("3. **P2 - 缺失列**: 添加缺失的列\n")
f.write("4. **P3 - 缺失默认值**: 添加默认值\n")
f.write("5. **P4 - 缺失索引**: 创建索引提升性能\n")
print(f"[OK] 核对结果已输出到: {OUTPUT_FILE}")
# 关闭连接
mysql_conn.close()
dm_conn.close()
# 输出汇总
print("\n" + "="*60)
print("核对汇总")
print("="*60)
print(f"缺失表: {len(results['missing_tables'])} 张")
print(f"缺失列: {len(results['missing_columns'])} 张表")
print(f"缺失默认值: {len(results['missing_defaults'])} 张表")
print(f"缺失索引: {len(results['missing_indexes'])} 张表")
print(f"数据差异: {len(results['data_diff'])} 张表")
if __name__ == '__main__':
complete_check()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DM_OFFLINE 库完整核对脚本
核对 MySQL offline 库和达梦 DM_OFFLINE 库的:
1. 表结构
2. 字段(列名、类型、长度、默认值)
3. 表数据
4. 表索引
5. 字段默认值
"""
import pymysql
import dmPython
import json
from collections import defaultdict
from datetime import datetime
# ==================== 配置信息 ====================
# MySQL 配置
MYSQL_HOST = '192.168.5.41'
MYSQL_PORT = 8306
MYSQL_USER = 'root'
MYSQL_PWD = 'dNrprU&2S'
MYSQL_DB = 'offline'
# 达梦配置
DM_HOST = '192.168.5.40'
DM_PORT = 5236
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_SCHEMA = 'DM_OFFLINE'
# 输出文件
OUTPUT_FILE = 'Docs/offline_complete_check.txt'
# ==================== 连接数据库 ====================
def connect_mysql():
"""连接 MySQL"""
try:
conn = pymysql.connect(
host=MYSQL_HOST,
port=MYSQL_PORT,
user=MYSQL_USER,
password=MYSQL_PWD,
database=MYSQL_DB,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
print(f"[OK] MySQL 连接成功: {MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}")
return conn
except Exception as e:
print(f"[ERROR] MySQL 连接失败: {e}")
return None
def connect_dm():
"""连接达梦"""
try:
conn = dmPython.connect(
user=DM_USER,
password=DM_PWD,
server=DM_HOST,
port=DM_PORT
)
print(f"[OK] 达梦连接成功: {DM_HOST}:{DM_PORT}/{DM_SCHEMA}")
return conn
except Exception as e:
print(f"[ERROR] 达梦连接失败: {e}")
return None
# ==================== 获取表列表 ====================
def get_mysql_tables(conn):
"""获取 MySQL 所有表名"""
cursor = conn.cursor()
cursor.execute("SHOW TABLES")
tables = [row[f'Tables_in_{MYSQL_DB}'] for row in cursor.fetchall()]
cursor.close()
return sorted(tables)
def get_dm_tables(conn):
"""获取达梦 DM_OFFLINE 所有表名"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT TABLE_NAME
FROM DBA_TABLES
WHERE OWNER = '{DM_SCHEMA}'
ORDER BY TABLE_NAME
""")
tables = [row[0] for row in cursor.fetchall()]
cursor.close()
return sorted(tables)
# ==================== 获取表结构 ====================
def get_mysql_columns(conn, table):
"""获取 MySQL 表的所有列信息"""
cursor = conn.cursor()
cursor.execute(f"SHOW FULL COLUMNS FROM {table}")
columns = cursor.fetchall()
cursor.close()
return columns
def get_dm_columns(conn, table):
"""获取达梦表的所有列信息"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT
COLUMN_NAME,
DATA_TYPE,
DATA_LENGTH,
DATA_PRECISION,
DATA_SCALE,
NULLABLE,
DATA_DEFAULT
FROM DBA_TAB_COLUMNS
WHERE OWNER = '{DM_SCHEMA}'
AND TABLE_NAME = '{table}'
ORDER BY COLUMN_ID
""")
rows = cursor.fetchall()
columns = []
for row in rows:
col_name = row[0]
col_type = row[1]
# 构建完整类型
if row[3] and row[4]: # DATA_PRECISION 和 DATA_SCALE 存在
col_type_full = f"{col_type}({row[3]},{row[4]})"
elif row[2]: # DATA_LENGTH 存在
col_type_full = f"{col_type}({row[2]})"
else:
col_type_full = col_type
columns.append({
'COLUMN_NAME': col_name,
'DATA_TYPE': col_type_full,
'NULLABLE': row[5],
'DATA_DEFAULT': row[6] if row[6] else None
})
cursor.close()
return columns
# ==================== 获取索引信息 ====================
def get_mysql_indexes(conn, table):
"""获取 MySQL 表的索引信息"""
cursor = conn.cursor()
cursor.execute(f"SHOW INDEX FROM {table}")
indexes = cursor.fetchall()
cursor.close()
# 按索引名分组
idx_dict = defaultdict(list)
for row in indexes:
idx_name = row['Key_name']
idx_dict[idx_name].append({
'COLUMN_NAME': row['Column_name'],
'NON_UNIQUE': row['Non_unique'],
'SEQ_IN_INDEX': row['Seq_in_index']
})
return idx_dict
def get_dm_indexes(conn, table):
"""获取达梦表的索引信息"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT
i.INDEX_NAME,
ic.COLUMN_NAME,
i.UNIQUENESS
FROM DBA_INDEXES i
JOIN DBA_IND_COLUMNS ic ON i.INDEX_NAME = ic.INDEX_NAME AND i.OWNER = ic.INDEX_OWNER
WHERE i.TABLE_OWNER = '{DM_SCHEMA}'
AND i.TABLE_NAME = '{table}'
ORDER BY i.INDEX_NAME, ic.COLUMN_POSITION
""")
rows = cursor.fetchall()
cursor.close()
# 按索引名分组
idx_dict = defaultdict(list)
for row in rows:
idx_name = row[0]
idx_dict[idx_name].append({
'COLUMN_NAME': row[1],
'UNIQUENESS': row[2]
})
return idx_dict
# ==================== 获取数据行数 ====================
def get_mysql_count(conn, table):
"""获取 MySQL 表行数"""
cursor = conn.cursor()
cursor.execute(f"SELECT COUNT(*) as cnt FROM {table}")
count = cursor.fetchone()['cnt']
cursor.close()
return count
def get_dm_count(conn, table):
"""获取达梦表行数"""
cursor = conn.cursor()
cursor.execute(f'SELECT COUNT(*) FROM "{DM_SCHEMA}"."{table}"')
count = cursor.fetchone()[0]
cursor.close()
return count
# ==================== 对比逻辑 ====================
def compare_tables(mysql_tables, dm_tables):
"""对比表列表"""
mysql_set = set([t.upper() for t in mysql_tables])
dm_set = set([t.upper() for t in dm_tables])
missing_in_dm = mysql_set - dm_set
extra_in_dm = dm_set - mysql_set
return missing_in_dm, extra_in_dm
def compare_columns(mysql_cols, dm_cols):
"""对比列结构"""
mysql_col_names = set([col['Field'].upper() for col in mysql_cols])
dm_col_names = set([col['COLUMN_NAME'].upper() for col in dm_cols])
missing_cols = mysql_col_names - dm_col_names
extra_cols = dm_col_names - mysql_col_names
# 对比默认值
missing_defaults = []
for mysql_col in mysql_cols:
col_name = mysql_col['Field'].upper()
mysql_default = mysql_col['Default']
# 找达梦对应的列
dm_col = None
for col in dm_cols:
if col['COLUMN_NAME'].upper() == col_name:
dm_col = col
break
if dm_col and mysql_default is not None:
# MySQL有默认值,检查达梦是否有
dm_default = dm_col['DATA_DEFAULT']
if dm_default is None:
missing_defaults.append({
'COLUMN': col_name,
'MYSQL_DEFAULT': mysql_default,
'DM_DEFAULT': None
})
return missing_cols, extra_cols, missing_defaults
def compare_indexes(mysql_indexes, dm_indexes):
"""对比索引"""
mysql_idx_names = set([k.upper() for k in mysql_indexes.keys()])
dm_idx_names = set([k.upper() for k in dm_indexes.keys()])
# 主键索引不对比(PRIMARY)
mysql_idx_names.discard('PRIMARY')
missing_indexes = mysql_idx_names - dm_idx_names
extra_indexes = dm_idx_names - mysql_idx_names
return missing_indexes, extra_indexes
# ==================== 主核对函数 ====================
def complete_check():
"""完整核对"""
print("\n" + "="*60)
print("DM_OFFLINE 库完整核对")
print("="*60 + "\n")
# 连接数据库
mysql_conn = connect_mysql()
dm_conn = connect_dm()
if not mysql_conn or not dm_conn:
return
# 获取表列表
mysql_tables = get_mysql_tables(mysql_conn)
dm_tables = get_dm_tables(dm_conn)
# 创建达梦表名映射(大写 -> 实际表名)
dm_table_map = {t.upper(): t for t in dm_tables}
print(f"\nMySQL 表数量: {len(mysql_tables)}")
print(f"达梦 表数量: {len(dm_tables)}\n")
# 对比表
missing_tables, extra_tables = compare_tables(mysql_tables, dm_tables)
results = {
'missing_tables': sorted(list(missing_tables)),
'extra_tables': sorted(list(extra_tables)),
'missing_columns': defaultdict(list),
'missing_defaults': defaultdict(list),
'missing_indexes': defaultdict(list),
'data_diff': []
}
# 逐表对比
print("开始逐表对比...")
for table in mysql_tables:
table_upper = table.upper()
# 检查表是否存在
if table_upper in missing_tables:
print(f" [缺失表] {table}")
continue
# 获取达梦实际表名
dm_table_name = dm_table_map.get(table_upper, table)
# 获取列信息
mysql_cols = get_mysql_columns(mysql_conn, table)
dm_cols = get_dm_columns(dm_conn, dm_table_name)
# 对比列
missing_cols, extra_cols, missing_defaults = compare_columns(mysql_cols, dm_cols)
if missing_cols:
results['missing_columns'][table_upper] = sorted(list(missing_cols))
print(f" [缺失列] {table}: {sorted(list(missing_cols))}")
if missing_defaults:
results['missing_defaults'][table_upper] = missing_defaults
print(f" [缺失默认值] {table}: {len(missing_defaults)} 个")
# 获取索引信息
mysql_indexes = get_mysql_indexes(mysql_conn, table)
dm_indexes = get_dm_indexes(dm_conn, dm_table_name)
# 对比索引
missing_idx, extra_idx = compare_indexes(mysql_indexes, dm_indexes)
if missing_idx:
results['missing_indexes'][table_upper] = sorted(list(missing_idx))
print(f" [缺失索引] {table}: {sorted(list(missing_idx))}")
# 对比数据行数
mysql_count = get_mysql_count(mysql_conn, table)
dm_count = get_dm_count(dm_conn, dm_table_name)
if mysql_count != dm_count:
results['data_diff'].append({
'TABLE': table_upper,
'MYSQL_COUNT': mysql_count,
'DM_COUNT': dm_count,
'DIFF': mysql_count - dm_count
})
print(f" [数据差异] {table}: MySQL {mysql_count} 行, 达梦 {dm_count} 行")
# 输出结果
print("\n" + "="*60)
print("核对完成,输出结果到文件")
print("="*60 + "\n")
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
f.write("# DM_OFFLINE 库完整核对结果\n\n")
f.write(f"核对时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write("## 一、表差异\n\n")
f.write(f"### 1.1 达梦缺失的表 ({len(results['missing_tables'])} 张)\n\n")
if results['missing_tables']:
for table in results['missing_tables']:
f.write(f"- {table}\n")
else:
f.write("[OK] 无缺失表\n")
f.write(f"\n### 1.2 达梦多余的表 ({len(results['extra_tables'])} 张)\n\n")
if results['extra_tables']:
for table in results['extra_tables']:
f.write(f"- {table}\n")
else:
f.write("[OK] 无多余表\n")
f.write("\n## 二、列差异\n\n")
f.write(f"### 2.1 缺失的列 ({len(results['missing_columns'])} 张表)\n\n")
if results['missing_columns']:
for table, cols in results['missing_columns'].items():
f.write(f"- **{table}**: {', '.join(cols)}\n")
else:
f.write("[OK] 无缺失列\n")
f.write("\n## 三、默认值差异\n\n")
f.write(f"### 3.1 缺失的默认值 ({len(results['missing_defaults'])} 张表)\n\n")
if results['missing_defaults']:
for table, defaults in results['missing_defaults'].items():
f.write(f"- **{table}**:\n")
for d in defaults:
f.write(f" - {d['COLUMN']}: MySQL默认值 `{d['MYSQL_DEFAULT']}`, 达梦无默认值\n")
else:
f.write("[OK] 无缺失默认值\n")
f.write("\n## 四、索引差异\n\n")
f.write(f"### 4.1 缺失的索引 ({len(results['missing_indexes'])} 张表)\n\n")
if results['missing_indexes']:
for table, indexes in results['missing_indexes'].items():
f.write(f"- **{table}**: {', '.join(indexes)}\n")
else:
f.write("[OK] 无缺失索引\n")
f.write("\n## 五、数据差异\n\n")
f.write(f"### 5.1 数据行数不一致 ({len(results['data_diff'])} 张表)\n\n")
if results['data_diff']:
# 按差异降序排序
sorted_diff = sorted(results['data_diff'], key=lambda x: abs(x['DIFF']), reverse=True)
for d in sorted_diff:
status = "[WARN]" if d['MYSQL_COUNT'] > d['DM_COUNT'] else "[OK]"
f.write(f"- {status} **{d['TABLE']}**: MySQL {d['MYSQL_COUNT']} 行, 达梦 {d['DM_COUNT']} 行, 差异 {d['DIFF']} 行\n")
else:
f.write("[OK] 数据行数一致\n")
f.write("\n## 六、修复建议\n\n")
f.write("### 6.1 优先级排序\n\n")
f.write("1. **P0 - 数据缺失**: 补充关键业务数据\n")
f.write("2. **P1 - 缺失表**: 创建缺失的表结构\n")
f.write("3. **P2 - 缺失列**: 添加缺失的列\n")
f.write("4. **P3 - 缺失默认值**: 添加默认值\n")
f.write("5. **P4 - 缺失索引**: 创建索引提升性能\n")
print(f"[OK] 核对结果已输出到: {OUTPUT_FILE}")
# 关闭连接
mysql_conn.close()
dm_conn.close()
# 输出汇总
print("\n" + "="*60)
print("核对汇总")
print("="*60)
print(f"缺失表: {len(results['missing_tables'])} 张")
print(f"缺失列: {len(results['missing_columns'])} 张表")
print(f"缺失默认值: {len(results['missing_defaults'])} 张表")
print(f"缺失索引: {len(results['missing_indexes'])} 张表")
print(f"数据差异: {len(results['data_diff'])} 张表")
if __name__ == '__main__':
complete_check()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
达梦数据库完整核对脚本 V2(统一版)
核对 MySQL 与达梦数据库的以下维度:
1. 表结构 - 表名列表(全部转大写对比)
2. 字段(列名、类型、长度、精度、可空、默认值) - 全部用大写对比
3. 索引(索引名、列名、列顺序、唯一性) - 全部用大写对比
4. 数据行数 - COUNT(*) 对比
5. 主键 - 对比主键字段
6. 外键 - 对比外键关系(如存在)
支持同时核对 DEVOPS 和 OFFLINE 两个库
"""
import pymysql
import dmPython
import sys
from collections import defaultdict
from datetime import datetime
# ==================== 配置信息 ====================
DB_CONFIGS = [
{
'name': 'DM_DEVOPS',
'mysql': {
'host': '192.168.5.41',
'port': 8306,
'user': 'root',
'pwd': 'dNrprU&2S',
'db': 'devops'
},
'dm': {
'host': '192.168.5.40',
'port': 5236,
'user': 'SYSDBA',
'pwd': 'Dameng2026Pwd',
'schema': 'DM_DEVOPS'
},
'skip_tables': [
'cmdb_servermonitor_date',
'cmdb_datemonitor',
'cmdb_servermonitor',
'uvoice_recordtime',
'django_session',
],
'output_file': 'Docs/dm_devops_complete_check_v2.txt'
},
{
'name': 'DM_OFFLINE',
'mysql': {
'host': '192.168.5.41',
'port': 8306,
'user': 'root',
'pwd': 'dNrprU&2S',
'db': 'offline'
},
'dm': {
'host': '192.168.5.40',
'port': 5236,
'user': 'SYSDBA',
'pwd': 'Dameng2026Pwd',
'schema': 'DM_OFFLINE'
},
'skip_tables': [],
'output_file': 'Docs/dm_offline_complete_check_v2.txt'
}
]
# ==================== MySQL 类型标准化 ====================
# 将 MySQL 类型映射到标准形式,便于与达梦对比
def normalize_mysql_type(col_type_raw):
"""标准化 MySQL 数据类型"""
col_type = col_type_raw.upper().strip()
# 去除多余的空格
col_type = ' '.join(col_type.split())
# UNSIGNED 修饰符不影响类型对比,去掉
col_type = col_type.replace('UNSIGNED', '').strip()
# VARCHAR(N) -> VARCHAR(N)
# INT -> INT
# DATETIME -> DATETIME
return col_type
# ==================== 达梦类型标准化 ====================
def normalize_dm_type(col_type_raw):
"""标准化达梦数据类型"""
col_type = col_type_raw.upper().strip()
# 达梦常用类型映射
type_mapping = {
'CHARACTER': 'CHAR',
'DOUBLE PRECISION': 'DOUBLE',
'DEC': 'DECIMAL',
'NUMERIC': 'DECIMAL',
'NATIONAL CHAR': 'NCHAR',
'NATIONAL CHARACTER': 'NCHAR',
'NCHAR VARYING': 'NVARCHAR',
'NATIONAL CHAR VARYING': 'NVARCHAR',
'NATIONAL CHARACTER VARYING': 'NVARCHAR',
}
# 替换类型名
for old, new in type_mapping.items():
if col_type.startswith(old):
col_type = col_type.replace(old, new, 1)
break
return col_type
# ==================== 连接数据库 ====================
def connect_mysql(config):
"""连接 MySQL"""
try:
conn = pymysql.connect(
host=config['host'],
port=config['port'],
user=config['user'],
password=config['pwd'],
database=config['db'],
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
print(f"[OK] MySQL 连接成功: {config['host']}:{config['port']}/{config['db']}")
return conn
except Exception as e:
print(f"[ERROR] MySQL 连接失败: {e}")
return None
def connect_dm(config):
"""连接达梦"""
try:
conn = dmPython.connect(
user=config['user'],
password=config['pwd'],
server=config['host'],
port=config['port']
)
print(f"[OK] 达梦连接成功: {config['host']}:{config['port']}/{config['schema']}")
return conn
except Exception as e:
print(f"[ERROR] 达梦连接失败: {e}")
return None
# ==================== 获取表列表 ====================
def get_mysql_tables(conn, db_name):
"""获取 MySQL 所有表名"""
cursor = conn.cursor()
cursor.execute("SHOW TABLES")
tables = [row[f'Tables_in_{db_name}'] for row in cursor.fetchall()]
cursor.close()
return sorted(tables)
def get_dm_tables(conn, schema):
"""获取达梦指定 schema 下所有表名"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT TABLE_NAME
FROM DBA_TABLES
WHERE OWNER = '{schema}'
ORDER BY TABLE_NAME
""")
tables = [row[0] for row in cursor.fetchall()]
cursor.close()
return sorted(tables)
# ==================== 获取字段信息 ====================
def get_mysql_columns(conn, table):
"""获取 MySQL 表的所有列详细信息"""
cursor = conn.cursor()
cursor.execute(f"SHOW FULL COLUMNS FROM `{table}`")
columns = cursor.fetchall()
cursor.close()
return columns
def get_dm_columns(conn, schema, table_name):
"""获取达梦表的所有列详细信息"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT
COLUMN_NAME,
DATA_TYPE,
DATA_LENGTH,
DATA_PRECISION,
DATA_SCALE,
NULLABLE,
DATA_DEFAULT
FROM DBA_TAB_COLUMNS
WHERE OWNER = '{schema}'
AND TABLE_NAME = '{table_name}'
ORDER BY COLUMN_ID
""")
rows = cursor.fetchall()
cursor.close()
columns = []
for row in rows:
col_name = row[0]
data_type = row[1].strip() if row[1] else ''
data_len = row[2]
data_prec = row[3]
data_scale = row[4]
nullable = row[5]
data_default = row[6]
# 构建完整类型字符串
if data_prec is not None and data_prec > 0:
if data_scale is not None and data_scale > 0:
type_full = f"{data_type}({data_prec},{data_scale})"
else:
type_full = f"{data_type}({data_prec})"
elif data_len is not None and data_len > 0:
type_full = f"{data_type}({data_len})"
else:
type_full = data_type
columns.append({
'COLUMN_NAME': col_name,
'DATA_TYPE': data_type,
'DATA_TYPE_FULL': type_full,
'DATA_LENGTH': data_len,
'DATA_PRECISION': data_prec,
'DATA_SCALE': data_scale,
'NULLABLE': nullable,
'DATA_DEFAULT': data_default
})
return columns
# ==================== 获取索引信息 ====================
def get_mysql_indexes(conn, table):
"""获取 MySQL 表的索引详细信息"""
cursor = conn.cursor()
cursor.execute(f"SHOW INDEX FROM `{table}`")
rows = cursor.fetchall()
cursor.close()
# 按索引名分组
idx_dict = defaultdict(list)
for row in rows:
idx_name = row['Key_name']
idx_dict[idx_name].append({
'COLUMN_NAME': row['Column_name'],
'NON_UNIQUE': row['Non_unique'],
'SEQ_IN_INDEX': row['Seq_in_index'],
'INDEX_TYPE': 'UNIQUE' if row['Non_unique'] == 0 else 'NONUNIQUE'
})
return idx_dict
def get_dm_indexes(conn, schema, table_name):
"""获取达梦表的索引详细信息"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT
i.INDEX_NAME,
ic.COLUMN_NAME,
i.UNIQUENESS,
ic.COLUMN_POSITION
FROM DBA_INDEXES i
JOIN DBA_IND_COLUMNS ic ON i.INDEX_NAME = ic.INDEX_NAME AND i.OWNER = ic.INDEX_OWNER
WHERE i.TABLE_OWNER = '{schema}'
AND i.TABLE_NAME = '{table_name}'
ORDER BY i.INDEX_NAME, ic.COLUMN_POSITION
""")
rows = cursor.fetchall()
cursor.close()
# 按索引名分组
idx_dict = defaultdict(list)
for row in rows:
idx_name = row[0]
idx_dict[idx_name].append({
'COLUMN_NAME': row[1],
'UNIQUENESS': row[2],
'COLUMN_POSITION': row[3]
})
return idx_dict
# ==================== 获取数据行数 ====================
def get_mysql_count(conn, table):
"""获取 MySQL 表行数"""
try:
cursor = conn.cursor()
cursor.execute(f"SELECT COUNT(*) as cnt FROM `{table}`")
count = cursor.fetchone()['cnt']
cursor.close()
return count
except Exception as e:
print(f" [ERROR] MySQL COUNT({table}) 失败: {e}")
return -1
def get_dm_count(conn, schema, table_name):
"""获取达梦表行数"""
try:
cursor = conn.cursor()
cursor.execute(f'SELECT COUNT(*) FROM "{schema}"."{table_name}"')
count = cursor.fetchone()[0]
cursor.close()
return count
except Exception as e:
print(f" [ERROR] 达梦 COUNT({table_name}) 失败: {e}")
return -1
# ==================== 主键信息 ====================
def get_mysql_primary_key(conn, table):
"""获取 MySQL 主键信息"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = '{conn.db}'
AND TABLE_NAME = '{table}'
AND CONSTRAINT_NAME = 'PRIMARY'
ORDER BY ORDINAL_POSITION
""")
cols = [row['COLUMN_NAME'] for row in cursor.fetchall()]
cursor.close()
return cols
def get_dm_primary_key(conn, schema, table_name):
"""获取达梦主键信息"""
cursor = conn.cursor()
cursor.execute(f"""
SELECT cc.COLUMN_NAME
FROM DBA_CONS_COLUMNS cc
JOIN DBA_CONSTRAINTS c ON cc.CONSTRAINT_NAME = c.CONSTRAINT_NAME
WHERE c.OWNER = '{schema}'
AND c.TABLE_NAME = '{table_name}'
AND c.CONSTRAINT_TYPE = 'P'
ORDER BY cc.POSITION
""")
cols = [row[0] for row in cursor.fetchall()]
cursor.close()
return cols
# ==================== 对比逻辑 ====================
def compare_tables(mysql_tables, dm_tables, skip_tables):
"""对比表列表,排除不迁移的表"""
# 统一转大写对比
mysql_upper = set([t.upper() for t in mysql_tables])
dm_upper = set([t.upper() for t in dm_tables])
skip_upper = set([t.upper() for t in skip_tables])
# 排除不迁移的表
mysql_active = mysql_upper - skip_upper
missing_in_dm = mysql_active - dm_upper
extra_in_dm = dm_upper - mysql_upper
return sorted(missing_in_dm), sorted(extra_in_dm)
def compare_columns(mysql_cols, dm_cols):
"""对比列结构:列名(大写)、类型、长度、是否可空、默认值"""
# 构建大写映射
mysql_col_map = {}
for col in mysql_cols:
key = col['Field'].upper()
mysql_col_map[key] = col
dm_col_map = {}
for col in dm_cols:
key = col['COLUMN_NAME'].upper()
dm_col_map[key] = col
mysql_col_names = set(mysql_col_map.keys())
dm_col_names = set(dm_col_map.keys())
# 1. 缺失的列(MySQL 有,达梦没有)
missing_cols = mysql_col_names - dm_col_names
# 2. 多余的列(达梦有,MySQL 没有)
extra_cols = dm_col_names - mysql_col_names
# 3. 缺失的默认值(MySQL 有默认值,达梦没有)
missing_defaults = []
for col_name in mysql_col_names:
mysql_col = mysql_col_map[col_name]
mysql_default = mysql_col['Default']
if mysql_default is not None and mysql_default != '':
dm_col = dm_col_map.get(col_name)
if dm_col:
dm_default = dm_col['DATA_DEFAULT']
if dm_default is None:
missing_defaults.append({
'COLUMN': col_name,
'MYSQL_DEFAULT': mysql_default,
'DM_DEFAULT': None
})
# 4. 默认值不一致(MySQL 和达梦都有默认值,但值不同)
diff_defaults = []
for col_name in mysql_col_names:
mysql_col = mysql_col_map[col_name]
mysql_default = mysql_col['Default']
if mysql_default is not None and mysql_default != '':
dm_col = dm_col_map.get(col_name)
if dm_col:
dm_default = dm_col['DATA_DEFAULT']
if dm_default is not None:
# 标准化后比较
mysql_str = str(mysql_default).upper().strip()
dm_str = str(dm_default).upper().strip()
# CURRENT_TIMESTAMP / SYSDATE 视为等价
mysql_str = mysql_str.replace('CURRENT_TIMESTAMP', 'SYSDATE')
dm_str = dm_str.replace('CURRENT_TIMESTAMP', 'SYSDATE')
if mysql_str != dm_str:
diff_defaults.append({
'COLUMN': col_name,
'MYSQL_DEFAULT': mysql_default,
'DM_DEFAULT': dm_default
})
# 5. 类型不匹配
type_diffs = []
for col_name in mysql_col_names:
if col_name in dm_col_map:
mysql_col = mysql_col_map[col_name]
dm_col = dm_col_map[col_name]
mysql_type = normalize_mysql_type(mysql_col['Type'])
dm_type_raw = normalize_dm_type(dm_col['DATA_TYPE_FULL'])
# 如果类型不一致,记录差异
if mysql_type != dm_type_raw:
# 特殊映射:MySQL 的 INT 与达梦的 INTEGER 等价
mysql_normalized = mysql_type.replace('INT ', 'INTEGER ').replace('INT(', 'INTEGER(').replace('INT', 'INTEGER')
dm_normalized = dm_type_raw.replace('INT ', 'INTEGER ').replace('INT(', 'INTEGER(').replace('INT', 'INTEGER')
# 特殊映射:BOOL/TINYINT(1) -> TINYINT
mysql_normalized = mysql_normalized.replace('BOOL', 'TINYINT')
mysql_normalized = mysql_normalized.replace('TINYINT(1)', 'TINYINT')
# 特殊映射:DATETIME -> TIMESTAMP
mysql_normalized = mysql_normalized.replace('DATETIME', 'TIMESTAMP')
# 特殊映射:LONGTEXT -> TEXT / CLOB
mysql_normalized = mysql_normalized.replace('LONGTEXT', 'CLOB')
mysql_normalized = mysql_normalized.replace('MEDIUMTEXT', 'CLOB')
if mysql_normalized != dm_normalized:
type_diffs.append({
'COLUMN': col_name,
'MYSQL_TYPE': mysql_type,
'DM_TYPE': dm_type_raw
})
# 6. 可空性不匹配
nullable_diffs = []
for col_name in mysql_col_names:
if col_name in dm_col_map:
mysql_col = mysql_col_map[col_name]
dm_col = dm_col_map[col_name]
mysql_nullable = mysql_col['Null'].upper() == 'YES'
dm_nullable = dm_col['NULLABLE'] == 'Y'
if mysql_nullable != dm_nullable:
nullable_diffs.append({
'COLUMN': col_name,
'MYSQL_NULLABLE': mysql_nullable,
'DM_NULLABLE': dm_nullable
})
return {
'missing_cols': sorted(missing_cols),
'extra_cols': sorted(extra_cols),
'missing_defaults': missing_defaults,
'diff_defaults': diff_defaults,
'type_diffs': type_diffs,
'nullable_diffs': nullable_diffs
}
def compare_indexes(mysql_indexes, dm_indexes):
"""对比索引:索引名(大写)、列名、列顺序、唯一性"""
mysql_upper = {}
for name, cols in mysql_indexes.items():
mysql_upper[name.upper()] = {
'ORIG_NAME': name,
'COLUMNS': cols
}
dm_upper = {}
for name, cols in dm_indexes.items():
dm_upper[name.upper()] = {
'ORIG_NAME': name,
'COLUMNS': cols
}
mysql_idx_names = set(mysql_upper.keys())
dm_idx_names = set(dm_upper.keys())
# 排除主键
mysql_idx_names.discard('PRIMARY')
dm_idx_names.discard('PRIMARY')
# 1. 缺失的索引(MySQL 有,达梦没有)
missing_indexes = mysql_idx_names - dm_idx_names
# 2. 多余的索引(达梦有,MySQL 没有)
extra_indexes = dm_idx_names - mysql_idx_names
# 3. 索引列不一致
column_diffs = []
for idx_name in mysql_idx_names & dm_idx_names:
mysql_cols = mysql_upper[idx_name]['COLUMNS']
dm_cols = dm_upper[idx_name]['COLUMNS']
mysql_col_str = ','.join([c['COLUMN_NAME'].upper() for c in sorted(mysql_cols, key=lambda x: x['SEQ_IN_INDEX'])])
dm_col_str = ','.join([c['COLUMN_NAME'].upper() for c in sorted(dm_cols, key=lambda x: x['COLUMN_POSITION'])])
if mysql_col_str != dm_col_str:
column_diffs.append({
'INDEX': idx_name,
'MYSQL_COLUMNS': mysql_col_str,
'DM_COLUMNS': dm_col_str
})
# 4. 唯一性不一致
uniqueness_diffs = []
for idx_name in mysql_idx_names & dm_idx_names:
mysql_is_unique = mysql_upper[idx_name]['COLUMNS'][0]['NON_UNIQUE'] == 0
dm_is_unique = dm_upper[idx_name]['COLUMNS'][0]['UNIQUENESS'] == 'UNIQUE'
if mysql_is_unique != dm_is_unique:
uniqueness_diffs.append({
'INDEX': idx_name,
'MYSQL_UNIQUE': mysql_is_unique,
'DM_UNIQUE': dm_is_unique
})
return {
'missing_indexes': sorted(missing_indexes),
'extra_indexes': sorted(extra_indexes),
'column_diffs': column_diffs,
'uniqueness_diffs': uniqueness_diffs
}
# ==================== 输出结果 ====================
def output_results(f, db_name, results):
"""输出核对结果到文件"""
f.write(f"\n{'='*80}\n")
f.write(f"# {db_name} 库完整核对结果 V2\n")
f.write(f"{'='*80}\n\n")
f.write(f"**核对时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
# ====== 表差异 ======
f.write("---\n\n")
f.write("## 一、表差异\n\n")
f.write(f"### 1.1 达梦缺失的表 ({len(results['missing_tables'])} 张)\n\n")
if results['missing_tables']:
for table in results['missing_tables']:
f.write(f"- {table}\n")
else:
f.write("> [OK] 无缺失表\n\n")
f.write(f"\n### 1.2 达梦多余的表 ({len(results['extra_tables'])} 张)\n\n")
if results['extra_tables']:
for table in results['extra_tables']:
f.write(f"- {table}\n")
else:
f.write("> [OK] 无多余表\n\n")
# ====== 列差异 ======
f.write("\n---\n\n")
f.write("## 二、列差异\n\n")
total_tables_with_issues = len(results['column_results'])
f.write(f"**涉及表数**: {total_tables_with_issues} 张\n\n")
# 统计
total_missing_cols = sum(1 for r in results['column_results'].values() if r['missing_cols'])
total_type_diffs = sum(1 for r in results['column_results'].values() if r['type_diffs'])
total_nullable_diffs = sum(1 for r in results['column_results'].values() if r['nullable_diffs'])
if total_tables_with_issues == 0:
f.write("> [OK] 所有字段信息完全一致\n\n")
else:
f.write(f"**汇总**: 缺失列 {total_missing_cols} 张表, 类型不匹配 {total_type_diffs} 张表, 可空性差异 {total_nullable_diffs} 张表\n\n")
# 2.1 缺失的列
f.write("### 2.1 缺失的列\n\n")
found = False
for table, cr in sorted(results['column_results'].items()):
if cr['missing_cols']:
found = True
f.write(f"- **{table}**: {', '.join(cr['missing_cols'])}\n")
if not found:
f.write("> [OK] 无缺失列\n\n")
# 2.2 多余的列
f.write("\n### 2.2 多余的列\n\n")
found = False
for table, cr in sorted(results['column_results'].items()):
if cr['extra_cols']:
found = True
f.write(f"- **{table}**: {', '.join(cr['extra_cols'])}\n")
if not found:
f.write("> [OK] 无多余列\n\n")
# 2.3 类型不匹配
f.write("\n### 2.3 类型不匹配\n\n")
found = False
for table, cr in sorted(results['column_results'].items()):
if cr['type_diffs']:
found = True
f.write(f"- **{table}**:\n")
for td in cr['type_diffs']:
f.write(f" - {td['COLUMN']}: MySQL=`{td['MYSQL_TYPE']}`, 达梦=`{td['DM_TYPE']}`\n")
if not found:
f.write("> [OK] 无类型不匹配\n\n")
# 2.4 可空性差异
f.write("\n### 2.4 可空性差异\n\n")
found = False
for table, cr in sorted(results['column_results'].items()):
if cr['nullable_diffs']:
found = True
f.write(f"- **{table}**:\n")
for nd in cr['nullable_diffs']:
f.write(f" - {nd['COLUMN']}: MySQL可空={nd['MYSQL_NULLABLE']}, 达梦可空={nd['DM_NULLABLE']}\n")
if not found:
f.write("> [OK] 无可空性差异\n\n")
# ====== 默认值差异 ======
f.write("\n---\n\n")
f.write("## 三、默认值差异\n\n")
total_default_issues = len(results['default_results'])
if total_default_issues == 0:
f.write("> [OK] 所有默认值一致\n\n")
else:
# 3.1 缺失的默认值
f.write("### 3.1 缺失的默认值(MySQL有默认值,达梦没有)\n\n")
found = False
for table, dr in sorted(results['default_results'].items()):
if dr['missing_defaults']:
found = True
f.write(f"- **{table}** ({len(dr['missing_defaults'])} 个):\n")
for d in dr['missing_defaults']:
f.write(f" - {d['COLUMN']}: MySQL默认值=`{d['MYSQL_DEFAULT']}`, 达梦=无\n")
if not found:
f.write("> [OK] 无缺失默认值\n\n")
# 3.2 默认值不一致
f.write("\n### 3.2 默认值不一致(MySQL和达梦都有但是值不同)\n\n")
found = False
for table, dr in sorted(results['default_results'].items()):
if dr['diff_defaults']:
found = True
f.write(f"- **{table}**:\n")
for d in dr['diff_defaults']:
f.write(f" - {d['COLUMN']}: MySQL=`{d['MYSQL_DEFAULT']}`, 达梦=`{d['DM_DEFAULT']}`\n")
if not found:
f.write("> [OK] 无不一致默认值\n\n")
# ====== 索引差异 ======
f.write("\n---\n\n")
f.write("## 四、索引差异\n\n")
total_idx_issues = len(results['index_results'])
if total_idx_issues == 0:
f.write("> [OK] 所有索引完全一致\n\n")
else:
# 统计
total_missing_idx = sum(1 for r in results['index_results'].values() if r['missing_indexes'])
total_extra_idx = sum(1 for r in results['index_results'].values() if r['extra_indexes'])
total_col_diff = sum(1 for r in results['index_results'].values() if r['column_diffs'])
total_uniq_diff = sum(1 for r in results['index_results'].values() if r['uniqueness_diffs'])
f.write(f"**汇总**: 缺失索引 {total_missing_idx} 张表, 多余索引 {total_extra_idx} 张表, "
f"列不一致 {total_col_diff} 张表, 唯一性差异 {total_uniq_diff} 张表\n\n")
# 4.1 缺失的索引
f.write("### 4.1 缺失的索引(MySQL有,达梦没有)\n\n")
found = False
for table, ir in sorted(results['index_results'].items()):
if ir['missing_indexes']:
found = True
f.write(f"- **{table}**: {', '.join(ir['missing_indexes'])}\n")
if not found:
f.write("> [OK] 无缺失索引\n\n")
# 4.2 多余的索引
f.write("\n### 4.2 多余的索引(达梦有,MySQL没有)\n\n")
found = False
for table, ir in sorted(results['index_results'].items()):
if ir['extra_indexes']:
found = True
f.write(f"- **{table}**: {', '.join(ir['extra_indexes'])}\n")
if not found:
f.write("> [OK] 无多余索引\n\n")
# 4.3 索引列不一致
f.write("\n### 4.3 索引列不一致(索引名相同但列不同)\n\n")
found = False
for table, ir in sorted(results['index_results'].items()):
if ir['column_diffs']:
found = True
f.write(f"- **{table}**:\n")
for cd in ir['column_diffs']:
f.write(f" - {cd['INDEX']}: MySQL列=`{cd['MYSQL_COLUMNS']}`, 达梦列=`{cd['DM_COLUMNS']}`\n")
if not found:
f.write("> [OK] 无索引列不一致\n\n")
# 4.4 唯一性差异
f.write("\n### 4.4 唯一性差异(索引名相同但唯一性不同)\n\n")
found = False
for table, ir in sorted(results['index_results'].items()):
if ir['uniqueness_diffs']:
found = True
f.write(f"- **{table}**:\n")
for ud in ir['uniqueness_diffs']:
f.write(f" - {ud['INDEX']}: MySQL唯一={ud['MYSQL_UNIQUE']}, 达梦唯一={ud['DM_UNIQUE']}\n")
if not found:
f.write("> [OK] 无唯一性差异\n\n")
# ====== 数据差异 ======
f.write("\n---\n\n")
f.write("## 五、数据差异\n\n")
if results['data_diff']:
f.write(f"**数据行数不一致的表 ({len(results['data_diff'])} 张)**\n\n")
f.write("| 表名 | MySQL行数 | 达梦行数 | 差异 | 状态 |\n")
f.write("|------|-----------|----------|------|------|\n")
sorted_diff = sorted(results['data_diff'], key=lambda x: abs(x['DIFF']), reverse=True)
for d in sorted_diff:
status = "❌ 缺失" if d['DIFF'] > 0 else "⚠️ 多余"
f.write(f"| {d['TABLE']} | {d['MYSQL_COUNT']} | {d['DM_COUNT']} | {d['DIFF']} | {status} |\n")
f.write("\n")
else:
f.write("> [OK] 所有表数据行数一致\n\n")
# ====== 跳过表 ======
if results['skip_tables']:
f.write("\n---\n\n")
f.write("## 六、不迁移的表(已跳过核对)\n\n")
for table in results['skip_tables']:
f.write(f"- {table}\n")
f.write("\n")
# ==================== 主核对函数 ====================
def check_database(db_config):
"""完整核对一个数据库"""
db_name = db_config['name']
print(f"\n{'='*70}")
print(f"开始核对 {db_name}")
print(f"{'='*70}\n")
mysql_cfg = db_config['mysql']
dm_cfg = db_config['dm']
skip_tables = db_config['skip_tables']
output_file = db_config['output_file']
# 连接数据库
mysql_conn = connect_mysql(mysql_cfg)
dm_conn = connect_dm(dm_cfg)
if not mysql_conn or not dm_conn:
return None
print("\n[信息] 获取表列表...")
# 获取表列表
mysql_tables = get_mysql_tables(mysql_conn, mysql_cfg['db'])
dm_tables = get_dm_tables(dm_conn, dm_cfg['schema'])
# 构建达梦表名映射(大写 -> 实际表名)
dm_table_map = {t.upper(): t for t in dm_tables}
# 同样构建 MySQL 表名映射
mysql_table_map = {t.upper(): t for t in mysql_tables}
print(f"MySQL 表数量: {len(mysql_tables)}")
print(f"达梦 表数量: {len(dm_tables)}")
print(f"跳过核对的表: {skip_tables}")
# 对比表
missing_tables, extra_tables = compare_tables(mysql_tables, dm_tables, skip_tables)
results = {
'db_name': db_name,
'mysql_tables': mysql_tables,
'dm_tables': dm_tables,
'missing_tables': missing_tables,
'extra_tables': extra_tables,
'column_results': defaultdict(dict),
'default_results': defaultdict(dict),
'index_results': defaultdict(dict),
'data_diff': [],
'skip_tables': skip_tables
}
# 处理跳过表的表名(小写)
skip_lower = set([t.lower() for t in skip_tables])
# 逐表对比
print("\n开始逐表核对...")
table_count = 0
for mysql_table in mysql_tables:
table_upper = mysql_table.upper()
# 跳过不迁移的表
if mysql_table.lower() in skip_lower:
print(f" [跳过] {mysql_table} (不迁移的表)")
continue
# 检查表是否存在
if table_upper in missing_tables:
print(f" [缺失表] {mysql_table}")
continue
# 获取达梦实际表名
dm_table_name = dm_table_map.get(table_upper, mysql_table)
table_count += 1
# ----- 获取字段信息 -----
mysql_cols = get_mysql_columns(mysql_conn, mysql_table)
dm_cols = get_dm_columns(dm_conn, dm_cfg['schema'], dm_table_name)
# 对比字段
col_result = compare_columns(mysql_cols, dm_cols)
has_issues = (col_result['missing_cols'] or col_result['extra_cols'] or
col_result['type_diffs'] or col_result['nullable_diffs'])
if col_result['missing_cols']:
print(f" [缺失列] {mysql_table}: {col_result['missing_cols']}")
if col_result['extra_cols']:
print(f" [多余列] {mysql_table}: {col_result['extra_cols']}")
if col_result['type_diffs']:
print(f" [类型不匹配] {mysql_table}: {len(col_result['type_diffs'])} 个")
if col_result['nullable_diffs']:
print(f" [可空差异] {mysql_table}: {len(col_result['nullable_diffs'])} 个")
# 记录字段差异
if has_issues:
results['column_results'][table_upper] = col_result
# ----- 默认值差异 -----
has_default_issues = (col_result['missing_defaults'] or col_result['diff_defaults'])
if col_result['missing_defaults']:
print(f" [缺失默认值] {mysql_table}: {len(col_result['missing_defaults'])} 个")
if col_result['diff_defaults']:
print(f" [默认值不一致] {mysql_table}: {len(col_result['diff_defaults'])} 个")
if has_default_issues:
results['default_results'][table_upper] = {
'missing_defaults': col_result['missing_defaults'],
'diff_defaults': col_result['diff_defaults']
}
# ----- 获取索引信息 -----
mysql_indexes = get_mysql_indexes(mysql_conn, mysql_table)
dm_indexes = get_dm_indexes(dm_conn, dm_cfg['schema'], dm_table_name)
# 对比索引
idx_result = compare_indexes(mysql_indexes, dm_indexes)
has_idx_issues = (idx_result['missing_indexes'] or idx_result['extra_indexes'] or
idx_result['column_diffs'] or idx_result['uniqueness_diffs'])
if idx_result['missing_indexes']:
print(f" [缺失索引] {mysql_table}: {len(idx_result['missing_indexes'])} 个")
if idx_result['extra_indexes']:
print(f" [多余索引] {mysql_table}: {len(idx_result['extra_indexes'])} 个")
if idx_result['column_diffs']:
print(f" [索引列不一致] {mysql_table}: {len(idx_result['column_diffs'])} 个")
if idx_result['uniqueness_diffs']:
print(f" [唯一性差异] {mysql_table}: {len(idx_result['uniqueness_diffs'])} 个")
if has_idx_issues:
results['index_results'][table_upper] = idx_result
# ----- 对比数据行数 -----
mysql_count = get_mysql_count(mysql_conn, mysql_table)
dm_count = get_dm_count(dm_conn, dm_cfg['schema'], dm_table_name)
if mysql_count >= 0 and dm_count >= 0 and mysql_count != dm_count:
results['data_diff'].append({
'TABLE': table_upper,
'MYSQL_COUNT': mysql_count,
'DM_COUNT': dm_count,
'DIFF': mysql_count - dm_count
})
print(f" [数据差异] {mysql_table}: MySQL {mysql_count} 行, 达梦 {dm_count} 行")
print(f"\n[信息] 共核对 {table_count} 张表")
# 输出结果到文件
print(f"\n[信息] 输出结果到文件: {output_file}")
with open(output_file, 'w', encoding='utf-8') as f:
output_results(f, db_name, results)
print(f"[OK] 结果已输出到: {output_file}")
# 关闭连接
mysql_conn.close()
dm_conn.close()
return results
def print_summary(results):
"""输出核对汇总"""
if not results:
return
print(f"\n{'='*50}")
print(f"核对汇总 - {results['db_name']}")
print(f"{'='*50}")
print(f"MySQL 表数: {len(results['mysql_tables'])}")
print(f"达梦 表数: {len(results['dm_tables'])}")
print(f"缺失表: {len(results['missing_tables'])} 张")
print(f"多余表: {len(results['extra_tables'])} 张")
print(f"")
col_issues = len(results['column_results'])
missing_cols = sum(1 for r in results['column_results'].values() if r.get('missing_cols'))
type_diffs = sum(1 for r in results['column_results'].values() if r.get('type_diffs'))
nullable_diffs = sum(1 for r in results['column_results'].values() if r.get('nullable_diffs'))
print(f"字段差异表: {col_issues} 张 (缺失列:{missing_cols}, 类型:{type_diffs}, 可空:{nullable_diffs})")
default_issues = len(results['default_results'])
missing_dft = sum(1 for r in results['default_results'].values() if r.get('missing_defaults'))
diff_dft = sum(1 for r in results['default_results'].values() if r.get('diff_defaults'))
print(f"默认值差异: {default_issues} 张 (缺失:{missing_dft}, 不一致:{diff_dft})")
idx_issues = len(results['index_results'])
missing_idx = sum(1 for r in results['index_results'].values() if r.get('missing_indexes'))
extra_idx = sum(1 for r in results['index_results'].values() if r.get('extra_indexes'))
print(f"索引差异表: {idx_issues} 张 (缺失:{missing_idx}, 多余:{extra_idx})")
print(f"数据差异: {len(results['data_diff'])} 张表")
for d in results['data_diff']:
print(f" {d['TABLE']}: MySQL {d['MYSQL_COUNT']} 行, 达梦 {d['DM_COUNT']} 行")
# ==================== 入口 ====================
def main():
"""主函数"""
print("=" * 60)
print("达梦数据库完整核对脚本 V2(统一版)")
print("核对时间: " + datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
print("=" * 60)
all_results = []
for db_config in DB_CONFIGS:
results = check_database(db_config)
if results:
all_results.append(results)
print_summary(results)
# 输出总汇总
print(f"\n{'='*60}")
print("全部核对完成!")
print(f"{'='*60}")
for r in all_results:
print(f" {r['db_name']}: 结果已保存到 Docs/")
print(f" 缺失表={len(r['missing_tables'])}, 字段差异={len(r['column_results'])}张表, "
f"默认值差异={len(r['default_results'])}张表, "
f"索引差异={len(r['index_results'])}张表, "
f"数据差异={len(r['data_diff'])}张表")
print("\n结果文件:")
for db_config in DB_CONFIGS:
print(f" - {db_config['output_file']}")
if __name__ == '__main__':
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
执行默认值修复 SQL
通过 dmPython 连接达梦数据库执行
"""
import dmPython
# 达梦配置
DM_HOST = '192.168.5.40'
DM_PORT = 5236
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_SCHEMA = 'DM_DEVOPS'
# SQL 文件路径
SQL_FILE = 'Docs/fix_devops_defaults_v2.sql'
def execute_sql_file():
"""执行 SQL 文件"""
print("\n" + "="*60)
print("执行默认值修复 SQL")
print("="*60 + "\n")
# 连接达梦
try:
conn = dmPython.connect(
user=DM_USER,
password=DM_PWD,
server=DM_HOST,
port=DM_PORT
)
print(f"[OK] 达梦连接成功: {DM_HOST}:{DM_PORT}")
except Exception as e:
print(f"[ERROR] 达梦连接失败: {e}")
return
cursor = conn.cursor()
# 读取 SQL 文件
try:
with open(SQL_FILE, 'r', encoding='utf-8') as f:
sql_content = f.read()
except Exception as e:
print(f"[ERROR] 读取 SQL 文件失败: {e}")
conn.close()
return
# 分割 SQL 语句(按分号分割)
sql_statements = []
for line in sql_content.split('\n'):
line = line.strip()
# 跳过注释和空行
if not line or line.startswith('--'):
continue
# 累积 SQL 语句
if line.endswith(';'):
sql_statements.append(line)
print(f"[INFO] 共读取 {len(sql_statements)} 条 SQL 语句\n")
# 执行 SQL
success_count = 0
error_count = 0
error_messages = []
for i, sql in enumerate(sql_statements, 1):
try:
cursor.execute(sql)
conn.commit()
success_count += 1
print(f"[{i}/{len(sql_statements)}] [OK] {sql[:80]}...")
except Exception as e:
error_count += 1
error_msg = f"[{i}/{len(sql_statements)}] [ERROR] {sql[:60]}... - {e}"
error_messages.append(error_msg)
print(error_msg)
# 关闭连接
cursor.close()
conn.close()
# 输出汇总
print("\n" + "="*60)
print("执行汇总")
print("="*60)
print(f"成功: {success_count}/{len(sql_statements)}")
print(f"失败: {error_count}/{len(sql_statements)}")
if error_messages:
print("\n失败详情:")
for msg in error_messages:
print(f" {msg}")
print("\n执行完成!")
if __name__ == '__main__':
execute_sql_file()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
执行 DM_DEVOPS 索引修复
通过 dmPython 连接达梦数据库执行
"""
import dmPython
# 达梦配置
DM_HOST = '192.168.5.40'
DM_PORT = 5236
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_SCHEMA = 'DM_DEVOPS'
# SQL 文件路径
SQL_FILE = 'Docs/fix_devops_indexes.sql'
def execute_index_fix():
"""执行索引修复"""
print("\n" + "="*60)
print("执行 DM_DEVOPS 索引修复")
print("="*60 + "\n")
# 连接达梦
try:
conn = dmPython.connect(
user=DM_USER,
password=DM_PWD,
server=DM_HOST,
port=DM_PORT
)
print(f"[OK] 达梦连接成功: {DM_HOST}:{DM_PORT}\n")
except Exception as e:
print(f"[ERROR] 达梦连接失败: {e}")
return
cursor = conn.cursor()
# 读取 SQL 文件
try:
with open(SQL_FILE, 'r', encoding='utf-8') as f:
sql_content = f.read()
except Exception as e:
print(f"[ERROR] 读取 SQL 文件失败: {e}")
conn.close()
return
# 分割 SQL 语句
sql_statements = []
for line in sql_content.split('\n'):
line = line.strip()
# 跳过注释和空行
if not line or line.startswith('--'):
continue
# 累积 SQL 语句
if line.endswith(';'):
sql_statements.append(line)
print(f"[INFO] 共读取 {len(sql_statements)} 条 SQL 语句\n")
# 执行 SQL
success_count = 0
error_count = 0
error_messages = []
for i, sql in enumerate(sql_statements, 1):
try:
cursor.execute(sql)
conn.commit()
success_count += 1
# 每10条打印一次进度
if i % 10 == 0:
print(f"[进度] 已处理 {i}/{len(sql_statements)} 条")
except Exception as e:
error_count += 1
# 只记录前10个错误
if error_count <= 10:
error_msg = f"[{i}/{len(sql_statements)}] [ERROR] {sql[:60]}... - {e}"
error_messages.append(error_msg)
print(error_msg)
# 关闭连接
cursor.close()
conn.close()
# 输出汇总
print("\n" + "="*60)
print("执行汇总")
print("="*60)
print(f"成功: {success_count}/{len(sql_statements)}")
print(f"失败: {error_count}/{len(sql_statements)}")
if error_messages:
print(f"\n前{len(error_messages)}个错误:")
for msg in error_messages:
print(f" {msg}")
print("\n执行完成!")
if __name__ == '__main__':
execute_index_fix()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
导出 DM_DEVOPS 库为 dmp 文件
通过 SSH 连接到 192.168.5.40 执行导出命令
"""
import paramiko
import os
from datetime import datetime
# SSH 配置
SSH_HOST = '192.168.5.40'
SSH_PORT = 22
SSH_USER = 'root'
SSH_PWD = 'Ubains@123'
# 达梦配置
DM_CONTAINER = 'dm8-server'
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_PORT = 5236
DM_SCHEMA = 'DM_DEVOPS'
# 导出文件名
EXPORT_FILE = 'DM_DEVOPS_20260712.dmp'
EXPORT_LOG = 'dexp_devops.log'
def export_dm_devops():
"""导出 DM_DEVOPS 库"""
print("\n" + "="*60)
print("导出 DM_DEVOPS 库")
print("="*60 + "\n")
# 连接 SSH
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(SSH_HOST, SSH_PORT, SSH_USER, SSH_PWD)
print(f"[OK] SSH 连接成功: {SSH_HOST}")
except Exception as e:
print(f"[ERROR] SSH 连接失败: {e}")
return
# 执行导出命令
dexp_cmd = f'''docker exec {DM_CONTAINER} /opt/dmdbms/bin/dexp {DM_USER}/{DM_PWD}@localhost:{DM_PORT} FILE={EXPORT_FILE} LOG={EXPORT_LOG} OWNER={DM_SCHEMA}'''
print(f"\n[执行] 导出命令...")
print(f" {dexp_cmd}\n")
stdin, stdout, stderr = ssh.exec_command(dexp_cmd)
# 等待执行完成
exit_status = stdout.channel.recv_exit_status()
# 输出结果
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
if output:
print(output)
if error:
print(error)
if exit_status == 0:
print(f"\n[OK] 导出成功: {EXPORT_FILE}")
else:
print(f"\n[ERROR] 导出失败,退出码: {exit_status}")
ssh.close()
return
# 检查文件是否存在
print(f"\n[检查] 验证导出文件...")
stdin, stdout, stderr = ssh.exec_command(f'docker exec {DM_CONTAINER} ls -lh /opt/dmdbms/bin/{EXPORT_FILE}')
output = stdout.read().decode('utf-8')
print(output)
# 复制文件到本地
print(f"\n[复制] 从容器复制到宿主机...")
ssh.exec_command(f'docker cp {DM_CONTAINER}:/opt/dmdbms/bin/{EXPORT_FILE} /tmp/{EXPORT_FILE}')
ssh.exec_command(f'docker cp {DM_CONTAINER}:/opt/dmdbms/bin/{EXPORT_LOG} /tmp/{EXPORT_LOG}')
print(f"[OK] 文件已复制到 /tmp/")
# 下载到本地
print(f"\n[下载] 下载到本地...")
local_path = f'Docs/PRD/达梦数据库/dmp/{EXPORT_FILE}'
sftp = ssh.open_sftp()
try:
sftp.get(f'/tmp/{EXPORT_FILE}', local_path)
print(f"[OK] 已下载到: {local_path}")
except Exception as e:
print(f"[ERROR] 下载失败: {e}")
# 下载日志文件
try:
sftp.get(f'/tmp/{EXPORT_LOG}', f'Docs/PRD/达梦数据库/dmp/{EXPORT_LOG}')
print(f"[OK] 日志文件已下载")
except:
pass
sftp.close()
ssh.close()
print("\n" + "="*60)
print("导出完成!")
print("="*60)
if __name__ == '__main__':
export_dm_devops()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
导出 DM_DEVOPS 库为 DMP 文件
使用 paramiko 连接 SSH 执行命令
"""
import paramiko
import os
# SSH 配置
SSH_HOST = '192.168.5.40'
SSH_PORT = 22
SSH_USER = 'root'
SSH_PWD = 'Ubains@123'
# 达梦配置
DM_CONTAINER = 'dm8-server'
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_PORT = 5236
DM_SCHEMA = 'DM_DEVOPS'
# 导出文件名
EXPORT_FILE = 'DM_DEVOPS_20260712.dmp'
EXPORT_LOG = 'dexp_devops.log'
def export_dm_devops():
"""导出 DM_DEVOPS 库"""
print("\n" + "="*60)
print("导出 DM_DEVOPS 库")
print("="*60 + "\n")
# 连接 SSH
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(SSH_HOST, SSH_PORT, SSH_USER, SSH_PWD)
print(f"[OK] SSH 连接成功: {SSH_HOST}")
except Exception as e:
print(f"[ERROR] SSH 连接失败: {e}")
return
# 执行导出命令
dexp_cmd = f'''docker exec {DM_CONTAINER} /opt/dmdbms/bin/dexp {DM_USER}/{DM_PWD}@localhost:{DM_PORT} FILE=/opt/dmdbms/bak/{EXPORT_FILE} LOG=/opt/dmdbms/bak/{EXPORT_LOG} OWNER={DM_SCHEMA}'''
print(f"\n[执行] 导出命令...")
print(f" {dexp_cmd}\n")
stdin, stdout, stderr = ssh.exec_command(dexp_cmd)
# 等待执行完成
exit_status = stdout.channel.recv_exit_status()
# 输出结果
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
if output:
print(output)
if error:
print(error)
if exit_status == 0:
print(f"\n[OK] 导出成功: {EXPORT_FILE}")
else:
print(f"\n[ERROR] 导出失败,退出码: {exit_status}")
ssh.close()
return
# 检查文件是否存在
print(f"\n[检查] 验证导出文件...")
stdin, stdout, stderr = ssh.exec_command(f'docker exec {DM_CONTAINER} ls -lh /opt/dmdbms/bak/{EXPORT_FILE}')
output = stdout.read().decode('utf-8')
print(output)
# 复制文件到宿主机
print(f"\n[复制] 从容器复制到宿主机...")
ssh.exec_command(f'docker cp {DM_CONTAINER}:/opt/dmdbms/bak/{EXPORT_FILE} /tmp/{EXPORT_FILE}')
ssh.exec_command(f'docker cp {DM_CONTAINER}:/opt/dmdbms/bak/{EXPORT_LOG} /tmp/{EXPORT_LOG}')
# 等待复制完成
import time
time.sleep(2)
print(f"[OK] 文件已复制到 /tmp/")
# 下载到本地
print(f"\n[下载] 下载到本地...")
local_path = f'Docs/PRD/达梦数据库/dmp/{EXPORT_FILE}'
sftp = ssh.open_sftp()
try:
sftp.get(f'/tmp/{EXPORT_FILE}', local_path)
print(f"[OK] 已下载到: {local_path}")
# 验证文件大小
file_size = os.path.getsize(local_path)
print(f"[INFO] 文件大小: {file_size / 1024 / 1024:.2f} MB")
except Exception as e:
print(f"[ERROR] 下载失败: {e}")
# 下载日志文件
try:
sftp.get(f'/tmp/{EXPORT_LOG}', f'Docs/PRD/达梦数据库/dmp/{EXPORT_LOG}')
print(f"[OK] 日志文件已下载")
except:
pass
sftp.close()
ssh.close()
print("\n" + "="*60)
print("导出完成!")
print("="*60)
if __name__ == '__main__':
export_dm_devops()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
导出 DM_OFFLINE 库为 dmp 文件
通过 SSH 连接到 192.168.5.40 执行导出命令
"""
import paramiko
import os
from datetime import datetime
# SSH 配置
SSH_HOST = '192.168.5.40'
SSH_PORT = 22
SSH_USER = 'root'
SSH_PWD = 'Ubains@123'
# 达梦配置
DM_CONTAINER = 'dm8-server'
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_PORT = 5236
DM_SCHEMA = 'DM_OFFLINE'
# 导出文件名
EXPORT_FILE = f'DM_OFFLINE_{datetime.now().strftime("%Y%m%d")}.dmp'
EXPORT_LOG = 'dexp_offline.log'
# 容器内导出路径(dmdba 用户可写)
DM_EXPORT_DIR = '/opt/dmdbms/bak'
# 本地保存路径
LOCAL_DIR = 'Docs/PRD/达梦数据库/dmp'
def export_dm_offline():
"""导出 DM_OFFLINE 库"""
print("\n" + "="*60)
print("导出 DM_OFFLINE 库")
print("="*60 + "\n")
# 连接 SSH
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(SSH_HOST, SSH_PORT, SSH_USER, SSH_PWD)
print(f"[OK] SSH 连接成功: {SSH_HOST}")
except Exception as e:
print(f"[ERROR] SSH 连接失败: {e}")
return
# 用 dmdba 用户执行 dexp,输出到 /opt/dmdbms/bak/
dexp_cmd = (
f'docker exec -u dmdba {DM_CONTAINER} '
f'/opt/dmdbms/bin/dexp {DM_USER}/{DM_PWD}@localhost:{DM_PORT} '
f'FILE={DM_EXPORT_DIR}/{EXPORT_FILE} '
f'LOG={DM_EXPORT_DIR}/{EXPORT_LOG} '
f'OWNER={DM_SCHEMA}'
)
print(f"\n[执行] 导出命令...")
print(f" 容器: {DM_CONTAINER} (用户: dmdba)")
print(f" 模式: {DM_SCHEMA}")
print(f" 文件: {DM_EXPORT_DIR}/{EXPORT_FILE}")
print(f" {dexp_cmd}\n")
stdin, stdout, stderr = ssh.exec_command(dexp_cmd)
# 等待执行完成
exit_status = stdout.channel.recv_exit_status()
# 输出结果
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
if output:
print(output)
if error:
print(error)
if exit_status == 0:
print(f"\n[OK] 导出成功: {EXPORT_FILE}")
else:
print(f"\n[ERROR] 导出失败,退出码: {exit_status}")
ssh.close()
return
# 检查文件是否存在
print(f"\n[检查] 验证导出文件...")
stdin, stdout, stderr = ssh.exec_command(
f'docker exec {DM_CONTAINER} ls -lh {DM_EXPORT_DIR}/{EXPORT_FILE}'
)
output = stdout.read().decode('utf-8')
print(output)
# 复制文件从容器到宿主机
print(f"\n[复制] 从容器复制到宿主机...")
stdin, stdout, stderr = ssh.exec_command(
f'docker cp {DM_CONTAINER}:{DM_EXPORT_DIR}/{EXPORT_FILE} /tmp/{EXPORT_FILE}'
)
print(stdout.read().decode() + stderr.read().decode())
stdin, stdout, stderr = ssh.exec_command(
f'docker cp {DM_CONTAINER}:{DM_EXPORT_DIR}/{EXPORT_LOG} /tmp/{EXPORT_LOG}'
)
print(stdout.read().decode() + stderr.read().decode())
print(f"[OK] 文件已复制到 /tmp/")
# 确保本地目录存在
os.makedirs(LOCAL_DIR, exist_ok=True)
# 下载到本地
print(f"\n[下载] 下载到本地...")
local_path = f'{LOCAL_DIR}/{EXPORT_FILE}'
sftp = ssh.open_sftp()
try:
sftp.get(f'/tmp/{EXPORT_FILE}', local_path)
file_size = os.path.getsize(local_path)
print(f"[OK] 已下载到: {local_path}")
print(f" 文件大小: {file_size / 1024:.1f} KB")
except Exception as e:
print(f"[ERROR] 下载失败: {e}")
# 下载日志文件
try:
sftp.get(f'/tmp/{EXPORT_LOG}', f'{LOCAL_DIR}/{EXPORT_LOG}')
print(f"[OK] 日志文件已下载到: {LOCAL_DIR}/{EXPORT_LOG}")
except Exception as e:
print(f"[INFO] 日志文件下载(可选): {e}")
sftp.close()
ssh.close()
print("\n" + "="*60)
print("导出完成!")
print(f"文件位置: {LOCAL_DIR}/{EXPORT_FILE}")
print("="*60)
if __name__ == '__main__':
export_dm_offline()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复 DM_DEVOPS 缺失的默认值
通过 dmPython 连接达梦数据库执行
"""
import dmPython
# 达梦配置
DM_HOST = '192.168.5.40'
DM_PORT = 5236
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_SCHEMA = 'DM_DEVOPS'
# 需要修复的默认值(表名: [(字段名, 默认值), ...])
FIX_DEFAULTS = {
'cmdb_company': [
('register', 0),
('api_secret', 0),
('SMART_HOME', 0), # 字段名是大写
('REMOTE_START', 0), # 字段名是大写
('REMOTE_END', 0), # 字段名是大写
],
'cmdb_companytype': [
('sort', 0),
],
'cmdb_customsetting': [
('sort', 0),
],
'cmdb_devicealarm': [
('type', 0),
],
'cmdb_firmware': [
('sort', 0),
],
'cmdb_inspection': [
('status', 0),
('runWay', 0), # 驼峰命名
('runType', 0), # 驼峰命名
('isEnable', 0), # 驼峰命名
('delflag', 0),
],
'cmdb_messagesetting': [
('sort', 0),
],
'cmdb_remotecontrol': [
('is_nat', 0),
],
'cmdb_roomdevice': [
('type', 0),
('useYear', 0), # 驼峰命名
('relateMaster', 0), # 驼峰命名
('outMaster', 0), # 驼峰命名
('IS_H5', 0), # 字段名是大写
],
'cmdb_roomgroup': [
('delflag', 0),
],
'cmdb_roommaster': [
('login_status', 0),
('login_time', 0),
('meetingtype', 0),
('maintenstatus', 0),
('roomstatus', 0),
('delflag', 0),
],
'cmdb_roomuser': [
('login_errnum', 0),
('login_status', 0),
('login_time', 0),
('delflag', 0),
],
'cmdb_roomuser_copy': [
('login_errnum', 0),
('login_status', 0),
('login_time', 0),
],
'uplayer_loginuser': [
('login_errnum', 0),
('login_status', 0),
('login_time', 0),
('authority', 0),
],
'uvoice_mailmanage': [
('mailssl', 1),
],
'uvoice_roomuser': [
('login_errnum', 0),
('login_status', 0),
('login_time', 0),
],
}
def fix_defaults():
"""修复默认值"""
print("\n" + "="*60)
print("修复 DM_DEVOPS 缺失的默认值")
print("="*60 + "\n")
# 连接达梦
try:
conn = dmPython.connect(
user=DM_USER,
password=DM_PWD,
server=DM_HOST,
port=DM_PORT
)
print(f"[OK] 达梦连接成功: {DM_HOST}:{DM_PORT}\n")
except Exception as e:
print(f"[ERROR] 达梦连接失败: {e}")
return
cursor = conn.cursor()
# 统计结果
success_count = 0
error_count = 0
# 逐表修复
for table, fields in FIX_DEFAULTS.items():
print(f"[表] {table}")
for field, default_value in fields:
try:
# 构建SQL(表名和字段名都是小写,需要用双引号包裹)
sql = f'ALTER TABLE "{DM_SCHEMA}"."{table}" MODIFY "{field}" INT DEFAULT {default_value}'
cursor.execute(sql)
conn.commit()
success_count += 1
print(f" [OK] {field} = {default_value}")
except Exception as e:
error_count += 1
print(f" [ERROR] {field} = {default_value}: {e}")
# 关闭连接
cursor.close()
conn.close()
# 输出汇总
print("\n" + "="*60)
print("修复汇总")
print("="*60)
print(f"成功: {success_count} 个字段")
print(f"失败: {error_count} 个字段")
print("\n修复完成!")
if __name__ == '__main__':
fix_defaults()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
修复 DM_DEVOPS 库核对 V2 发现的遗留问题
1. 修复缺失的 7 个索引
2. 修复数据差异(cmdb_roommonitor, uplayer_devicegroup, uplayer_devicemaster)
"""
import pymysql
import dmPython
from datetime import datetime
# ==================== 配置 ====================
MYSQL_HOST = '192.168.5.41'
MYSQL_PORT = 8306
MYSQL_USER = 'root'
MYSQL_PWD = 'dNrprU&2S'
MYSQL_DB = 'devops'
DM_HOST = '192.168.5.40'
DM_PORT = 5236
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_SCHEMA = 'DM_DEVOPS'
def connect_mysql():
conn = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER,
password=MYSQL_PWD, database=MYSQL_DB,
charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)
return conn
def connect_dm():
conn = dmPython.connect(user=DM_USER, password=DM_PWD, server=DM_HOST, port=DM_PORT)
return conn
# ==================== 修复缺失索引 ====================
def fix_missing_indexes(dm_conn):
"""修复缺失的 7 个索引"""
print("\n" + "="*60)
print("修复缺失的索引")
print("="*60 + "\n")
cursor = dm_conn.cursor()
# 缺失的索引列表
missing_indexes = [
# auth_group.name 索引
{
'name': 'AUTH_GROUP_NAME_IDX',
'sql': 'CREATE INDEX "DM_DEVOPS"."AUTH_GROUP_NAME_IDX" ON "DM_DEVOPS"."auth_group" ("name")'
},
# auth_user.username 索引
{
'name': 'AUTH_USER_USERNAME_IDX',
'sql': 'CREATE INDEX "DM_DEVOPS"."AUTH_USER_USERNAME_IDX" ON "DM_DEVOPS"."auth_user" ("username")'
},
# cmdb_roomcall.recoveruser_id 外键索引
{
'name': 'CMDB_ROOMCALL_RECOVERUSER_ID',
'sql': 'CREATE INDEX "DM_DEVOPS"."CMDB_ROOMCALL_RECOVERUSER_ID" ON "DM_DEVOPS"."cmdb_roomcall" ("recoveruser_id")'
},
# cmdb_roomdevice.compnay_id 外键索引(MySQL 拼写错误,达梦保持拼写一致)
{
'name': 'CMDB_ROOMDEVICE_COMPNAY_ID',
'sql': 'CREATE INDEX "DM_DEVOPS"."CMDB_ROOMDEVICE_COMPNAY_ID" ON "DM_DEVOPS"."cmdb_roomdevice" ("compnay_id")'
},
# cmdb_roommessage.compnay_id 外键索引
{
'name': 'CMDB_ROOMMESSAGE_COMPNAY_ID',
'sql': 'CREATE INDEX "DM_DEVOPS"."CMDB_ROOMMESSAGE_COMPNAY_ID" ON "DM_DEVOPS"."cmdb_roommessage" ("compnay_id")'
},
# cmdb_roomuser_copy 外键索引
{
'name': 'CMDB_ROOMUSER_COPY_AUTHUSER_ID',
'sql': 'CREATE INDEX "DM_DEVOPS"."CMDB_ROOMUSER_COPY_AUTHUSER_ID" ON "DM_DEVOPS"."cmdb_roomuser_copy" ("authuser_id")'
},
{
'name': 'CMDB_ROOMUSER_COPY_COMPANY_ID',
'sql': 'CREATE INDEX "DM_DEVOPS"."CMDB_ROOMUSER_COPY_COMPANY_ID" ON "DM_DEVOPS"."cmdb_roomuser_copy" ("company_id")'
},
# cmdb_serverlog_copy1 外键索引
{
'name': 'CMDB_SERVERLOG_COPY1_COMPANY_ID',
'sql': 'CREATE INDEX "DM_DEVOPS"."CMDB_SERVERLOG_COPY1_COMPANY_ID" ON "DM_DEVOPS"."cmdb_serverlog_copy1" ("company_id")'
},
{
'name': 'CMDB_SERVERLOG_COPY1_USER_ID',
'sql': 'CREATE INDEX "DM_DEVOPS"."CMDB_SERVERLOG_COPY1_USER_ID" ON "DM_DEVOPS"."cmdb_serverlog_copy1" ("user_id")'
},
{
'name': 'CMDB_SERVERLOG_COPY1_MASTER_ID',
'sql': 'CREATE INDEX "DM_DEVOPS"."CMDB_SERVERLOG_COPY1_MASTER_ID" ON "DM_DEVOPS"."cmdb_serverlog_copy1" ("master_id")'
},
]
success_count = 0
error_count = 0
for idx in missing_indexes:
try:
# 先检查索引是否已存在
cursor.execute(f"""
SELECT COUNT(*) FROM DBA_INDEXES
WHERE OWNER='{DM_SCHEMA}' AND INDEX_NAME='{idx['name']}'
""")
exists = cursor.fetchone()[0] > 0
if exists:
print(f" [跳过] {idx['name']} 已存在")
continue
cursor.execute(idx['sql'])
dm_conn.commit()
success_count += 1
print(f" [OK] {idx['name']}")
except Exception as e:
error_count += 1
print(f" [ERROR] {idx['name']}: {e}")
cursor.close()
print(f"\n索引修复完成: 成功 {success_count}, 失败 {error_count}")
# ==================== 修复数据差异 ====================
def get_mysql_columns(conn, table):
"""获取 MySQL 表的列名"""
cursor = conn.cursor()
cursor.execute(f"SHOW COLUMNS FROM `{table}`")
columns = [row['Field'] for row in cursor.fetchall()]
cursor.close()
return columns
def get_mysql_data(conn, table):
"""获取 MySQL 表的所有数据"""
cursor = conn.cursor()
cursor.execute(f"SELECT * FROM `{table}`")
rows = cursor.fetchall()
cursor.close()
return rows
def clear_dm_table(conn, table):
"""清空达梦表"""
cursor = conn.cursor()
cursor.execute(f'DELETE FROM "{DM_SCHEMA}"."{table}"')
conn.commit()
cursor.close()
print(f" [清空] {table}")
def insert_dm_data(conn, table, columns, rows):
"""插入数据到达梦表"""
cursor = conn.cursor()
success = 0
errors = 0
for row in rows:
try:
col_names = ', '.join([f'"{col}"' for col in columns])
placeholders = ', '.join(['?' for _ in columns])
values = []
for col in columns:
val = row.get(col)
if val is None:
values.append(None)
elif isinstance(val, datetime):
values.append(val.strftime('%Y-%m-%d %H:%M:%S'))
else:
values.append(val)
sql = f'INSERT INTO "{DM_SCHEMA}"."{table}" ({col_names}) VALUES ({placeholders})'
cursor.execute(sql, values)
success += 1
except Exception as e:
errors += 1
if errors <= 3:
print(f" [ERROR] 插入失败: {e}")
print(f" [DATA] {row}")
conn.commit()
cursor.close()
return success, errors
def get_dm_count(conn, table):
"""获取达梦表行数"""
cursor = conn.cursor()
cursor.execute(f'SELECT COUNT(*) FROM "{DM_SCHEMA}"."{table}"')
cnt = cursor.fetchone()[0]
cursor.close()
return cnt
def fix_data_diff(mysql_conn, dm_conn):
"""修复数据差异"""
print("\n" + "="*60)
print("修复数据差异")
print("="*60 + "\n")
# 需要修复的表
tables_to_fix = [
'cmdb_roommonitor',
'uplayer_devicegroup',
'uplayer_devicemaster',
]
for table in tables_to_fix:
print(f"\n[修复] {table}")
# 获取 MySQL 数据
columns = get_mysql_columns(mysql_conn, table)
mysql_rows = get_mysql_data(mysql_conn, table)
mysql_count = len(mysql_rows)
dm_count_before = get_dm_count(dm_conn, table)
print(f" MySQL: {mysql_count} 行, 达梦修复前: {dm_count_before} 行")
if mysql_count == dm_count_before:
print(f" [跳过] 数据已一致")
continue
if mysql_count == 0:
print(f" [跳过] MySQL 表为空")
continue
# 清空并重新插入所有数据
clear_dm_table(dm_conn, table)
success, errors = insert_dm_data(dm_conn, table, columns, mysql_rows)
dm_count_after = get_dm_count(dm_conn, table)
print(f" 插入: 成功 {success}, 失败 {errors}")
print(f" 达梦修复后: {dm_count_after} 行")
if dm_count_after == mysql_count:
print(f" [OK] {table} 数据一致!")
else:
print(f" [WARN] {table} 数据不一致: MySQL {mysql_count}, 达梦 {dm_count_after}")
# ==================== 主函数 ====================
def main():
print("="*60)
print("DM_DEVOPS 库遗留问题修复 V2")
print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("="*60)
# 连接数据库
dm_conn = connect_dm()
mysql_conn = connect_mysql()
if not dm_conn or not mysql_conn:
return
# 1. 修复缺失索引
fix_missing_indexes(dm_conn)
# 2. 修复数据差异
fix_data_diff(mysql_conn, dm_conn)
# 关闭连接
dm_conn.close()
mysql_conn.close()
print("\n" + "="*60)
print("修复完成!请重新运行核对脚本验证。")
print("="*60)
if __name__ == '__main__':
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DM_DEVOPS 数据迁移脚本
从 MySQL devops 库迁移数据到达梦 DM_DEVOPS 库
"""
import pymysql
import dmPython
from datetime import datetime
# ==================== 配置信息 ====================
# MySQL 配置
MYSQL_HOST = '192.168.5.41'
MYSQL_PORT = 8306
MYSQL_USER = 'root'
MYSQL_PWD = 'dNrprU&2S'
MYSQL_DB = 'devops'
# 达梦配置
DM_HOST = '192.168.5.40'
DM_PORT = 5236
DM_USER = 'SYSDBA'
DM_PWD = 'Dameng2026Pwd'
DM_SCHEMA = 'DM_DEVOPS'
# 需迁移的表(按外键依赖顺序)
MIGRATE_TABLES = [
# P0 - 关键业务数据
'cmdb_company', # 公司表(被多张表外键引用)
'cmdb_uploadimage', # 上传图片表
'cmdb_uploadvoice', # 上传语音表
'cmdb_uploadfile', # 上传文件表(被 roomfile 引用)
# P1 - 设备相关数据
'cmdb_roomdevice', # 房间设备表
'cmdb_deviceauth', # 设备认证表
'cmdb_roomfile', # 房间文件表
'cmdb_functionlist', # 功能列表
# P1 - 用户相关数据
'cmdb_roomuser', # 房间用户表
'cmdb_roomuser_copy', # 房间用户备份表
# P1 - 其他数据
'cmdb_roommonitor', # 房间监控表
'uplayer_devicegroup', # 播放器设备组
'uplayer_devicemaster', # 播放器设备主表
'uplayer_mediafile', # 播放器媒体文件
]
# ==================== 连接数据库 ====================
def connect_mysql():
"""连接 MySQL"""
try:
conn = pymysql.connect(
host=MYSQL_HOST,
port=MYSQL_PORT,
user=MYSQL_USER,
password=MYSQL_PWD,
database=MYSQL_DB,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
print(f"[OK] MySQL 连接成功: {MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}")
return conn
except Exception as e:
print(f"[ERROR] MySQL 连接失败: {e}")
return None
def connect_dm():
"""连接达梦"""
try:
conn = dmPython.connect(
user=DM_USER,
password=DM_PWD,
server=DM_HOST,
port=DM_PORT
)
print(f"[OK] 达梦连接成功: {DM_HOST}:{DM_PORT}/{DM_SCHEMA}")
return conn
except Exception as e:
print(f"[ERROR] 达梦连接失败: {e}")
return None
# ==================== 数据迁移函数 ====================
def get_mysql_columns(conn, table):
"""获取 MySQL 表的列名列表"""
cursor = conn.cursor()
cursor.execute(f"SHOW COLUMNS FROM {table}")
columns = [row['Field'] for row in cursor.fetchall()]
cursor.close()
return columns
def get_mysql_data(conn, table):
"""获取 MySQL 表的所有数据"""
cursor = conn.cursor()
cursor.execute(f"SELECT * FROM {table}")
rows = cursor.fetchall()
cursor.close()
return rows
def clear_dm_table(conn, table):
"""清空达梦表数据"""
cursor = conn.cursor()
try:
# 达梦表名可能是小写,需用双引号
cursor.execute(f'DELETE FROM "{DM_SCHEMA}"."{table}"')
conn.commit()
print(f" [清空] {table}")
return True
except Exception as e:
print(f" [ERROR] 清空 {table} 失败: {e}")
return False
finally:
cursor.close()
def insert_dm_data(conn, table, columns, rows):
"""插入数据到达梦表"""
cursor = conn.cursor()
success_count = 0
error_count = 0
for row in rows:
try:
# 构建INSERT语句
col_names = ', '.join([f'"{col}"' for col in columns])
placeholders = ', '.join(['?' for _ in columns])
# 处理特殊值
values = []
for col in columns:
val = row.get(col)
if val is None:
values.append(None)
elif isinstance(val, datetime):
values.append(val.strftime('%Y-%m-%d %H:%M:%S'))
else:
values.append(val)
sql = f'INSERT INTO "{DM_SCHEMA}"."{table}" ({col_names}) VALUES ({placeholders})'
cursor.execute(sql, values)
success_count += 1
# 每100行提交一次
if success_count % 100 == 0:
conn.commit()
except Exception as e:
error_count += 1
if error_count <= 5: # 只打印前5个错误
print(f" [ERROR] 插入 {table} 失败: {e}")
print(f" [ERROR] 问题数据: {row}")
# 最终提交
conn.commit()
cursor.close()
return success_count, error_count
def get_dm_count(conn, table):
"""获取达梦表行数"""
cursor = conn.cursor()
cursor.execute(f'SELECT COUNT(*) FROM "{DM_SCHEMA}"."{table}"')
count = cursor.fetchone()[0]
cursor.close()
return count
def migrate_table(mysql_conn, dm_conn, table_name):
"""迁移单张表的数据"""
print(f"\n[迁移] {table_name}")
# 1. 获取 MySQL 数据
columns = get_mysql_columns(mysql_conn, table_name)
mysql_rows = get_mysql_data(mysql_conn, table_name)
mysql_count = len(mysql_rows)
print(f" MySQL 数据: {mysql_count} 行, {len(columns)} 列")
if mysql_count == 0:
print(f" [跳过] MySQL 表为空")
return True
# 2. 清空达梦表
if not clear_dm_table(dm_conn, table_name):
return False
# 3. 插入数据
success, error = insert_dm_data(dm_conn, table_name, columns, mysql_rows)
print(f" 插入成功: {success} 行, 失败: {error} 行")
# 4. 验证数据
dm_count = get_dm_count(dm_conn, table_name)
print(f" 达梦数据: {dm_count} 行")
if dm_count == mysql_count:
print(f" [OK] 数据一致")
return True
else:
print(f" [WARN] 数据不一致: MySQL {mysql_count} 行, 达梦 {dm_count} 行")
return False
# ==================== 主函数 ====================
def main():
"""主函数"""
print("\n" + "="*60)
print("DM_DEVOPS 数据迁移")
print("="*60 + "\n")
# 连接数据库
mysql_conn = connect_mysql()
dm_conn = connect_dm()
if not mysql_conn or not dm_conn:
return
# 统计结果
results = {
'success': [],
'failed': [],
'skipped': []
}
# 迁移每张表
for table in MIGRATE_TABLES:
try:
result = migrate_table(mysql_conn, dm_conn, table)
if result:
results['success'].append(table)
else:
results['failed'].append(table)
except Exception as e:
print(f" [ERROR] 迁移 {table} 异常: {e}")
results['failed'].append(table)
# 输出汇总
print("\n" + "="*60)
print("迁移汇总")
print("="*60)
print(f"成功: {len(results['success'])} 张表")
for table in results['success']:
print(f" - {table}")
print(f"\n失败: {len(results['failed'])} 张表")
for table in results['failed']:
print(f" - {table}")
# 关闭连接
mysql_conn.close()
dm_conn.close()
print("\n迁移完成!")
if __name__ == '__main__':
main()
---
name: handoff
description: 会话交接文档生成 - 生成 HANDOFF.md 结构化交接文档,记录任务进展、阻塞项、下一步计划与踩坑警示
---
会话结束时生成结构化交接文档 `HANDOFF.md`,供完全没有上下文的新会话快速接手工作。
## Usage
```
/handoff # 生成 HANDOFF.md 交接文档
/handoff --append # 追加到现有 HANDOFF.md(而不是覆盖)
```
## 执行流程
### 阶段 1:交互式信息收集
**按以下顺序询问用户(使用 `AskUserQuestion`)**
#### Q1:任务概述
> **我们在做什么任务?**
>
> 简要描述当前会话的核心目标(一句话),例如:
> - "实现达梦数据库自动化迁移流程"
> - "为 X86-QLV10-XTYBS 技能添加回滚机制"
> - "排查 DM8 容器启动失败问题"
>
> [用户输入任务描述]
#### Q2:已完成事项
> **已经完成了什么?**
>
> 列出本会话中已完成的具体产出(代码文件、文档、脚本、配置变更等):
> 1. `文件路径` — 简述其作用
> 2. ...
>
> 如无实质产出,说明"仅完成调研/设计,未产出代码"。
>
> [用户输入已完成列表]
#### Q3:当前阻塞项
> **当前卡在哪?**
>
> 描述阻塞点及原因:
> - 技术阻塞(报错、环境问题、依赖缺失)
> - 信息阻塞(缺少配置、权限、目标 IP)
> - 决策阻塞(方案待定、需用户确认)
>
> 如已顺利完成,回答"无阻塞,任务已完成"。
>
> [用户输入阻塞项描述]
#### Q4:下一步计划
> **下一步计划是什么?**
>
> 列出接手者需要执行的具体步骤(按顺序):
> 1. [动作] — 预期结果
> 2. [动作] — 预期结果
> 3. ...
>
> 如任务已完成,说明"无需后续步骤,可关闭此任务"。
>
> [用户输入下一步计划]
#### Q5:踩坑警示
> **有哪些踩过的坑绝对不要再踩?**
>
> 记录本会话中遇到的错误陷阱、误操作、误导性尝试:
> - ❌ 不要...(原因)
> - ❌ 不要...(原因)
> - ❌ 不要...(原因)
>
> 如无踩坑,说明"未遇到明显坑点"。
>
> [用户输入踩坑列表]
---
### 阶段 2:生成 HANDOFF.md
根据用户输入,按以下模板生成交接文档:
```markdown
# HANDOFF — 会话交接文档
> **生成时间**:YYYY-MM-DD HH:MM
> **会话 ID**:[当前会话标识,如有]
---
## 任务概述
[用户输入的任务描述]
---
## 已完成事项
| # | 产出项 | 位置/路径 | 说明 |
|---|--------|-----------|------|
| 1 | ... | `...` | ... |
| 2 | ... | `...` | ... |
或:仅完成调研/设计,未产出代码。
---
## 当前阻塞项
| 类型 | 描述 | 原因 |
|------|------|------|
| 技术/信息/决策 | ... | ... |
或:无阻塞,任务已完成。
---
## 下一步计划
| # | 步骤 | 预期结果 |
|---|------|----------|
| 1 | ... | ... |
| 2 | ... | ... |
或:无需后续步骤,可关闭此任务。
---
## ⚠️ 踩坑警示 — 绝对不要再踩
1.**不要**...(原因)
2.**不要**...(原因)
3.**不要**...(原因)
或:未遇到明显坑点。
---
## 附录:关键文件/资源链接
| 资源类型 | 位置 | 备注 |
|----------|------|------|
| PRD 文档 | `...` | ... |
| 配置文件 | `...` | ... |
| 日志文件 | `...` | ... |
| 参考链接 | `...` | ... |
(此部分由用户补充或留空)
---
## 给接手者的话
> [用户可在此留下简短备注,如联系方式、时间约束、依赖方状态等]
(可选)
```
### 阶段 3:写入文件
1. 检查当前工作目录是否已存在 `HANDOFF.md`
2. 如果存在:
- 默认:询问用户是**覆盖**还是**追加**
- `--append` 参数:直接追加到现有文档末尾(添加时间戳分隔)
3. 写入/追加后,展示生成的文档内容供用户确认
4. 提示用户:
> ✅ 已生成 `HANDOFF.md`。
>
> 💡 建议:在开始新会话时,先执行 `Read HANDOFF.md` 了解上下文。
## 输出文件规范
- **位置**:当前工作目录根目录(即 `E:\github\ubains-module-test\develop\HANDOFF.md`
- **编码**:UTF-8
- **格式**:Markdown(兼容 GitHub/GitLab 渲染)
- **命名**:固定为 `HANDOFF.md`(大小写敏感)
## 注意事项
- **交互优先**:不自动推断任务内容,必须由用户明确输入
- **结构固定**:五部分内容(概述、已完成、阻塞、下一步、踩坑)必须齐全
- **简洁原则**:每个表格条目不超过一行,避免长篇叙述
- **新会话友好**:假设接手者完全无上下文,描述要具体明确(文件路径、具体错误、步骤动作)
- **踩坑突出**:用 `❌` 符号和加粗强调,警示作用要强
- **时间戳记录**:每次生成/追加都记录时间,便于追溯多轮交接
- **不删除旧内容**:追加模式下保留历史交接记录,形成追溯链
## 常见问题
### Q:如果任务中途需要交接怎么办?
A:正常调用 `/handoff`,阻塞项写"任务未完成,因时间/会话中断",下一步计划详细列出未执行步骤。
### Q:如果接手者发现 HANDOFF.md 信息过时怎么办?
A:接手者应在新会话开始时阅读 `HANDOFF.md`,核实后如有变化可调用 `/handoff --append` 更新状态。
### Q:多人协作项目如何使用?
A:每次交接都追加(`--append`),形成多人接力记录。可在"给接手者的话"部分备注交接人姓名。
\ No newline at end of file
......@@ -13,6 +13,9 @@
| 7 | 执行数据迁移 | ✅ 已完成 | Claude Code | 2026-07-12 |
| 8 | 执行索引修复 | ✅ 已完成 | Claude Code | 2026-07-12 |
| 9 | 验证修复结果 | ✅ 已完成 | Claude Code | 2026-07-12 |
| 10 | V2 全面核对(6维度) | ✅ 已完成 | Claude Code | 2026-07-13 |
| 11 | V2 遗留问题修复 | ✅ 已完成 | Claude Code | 2026-07-13 |
| 12 | V2 验证 | ✅ 已完成 | Claude Code | 2026-07-13 |
---
......
#!/bin/bash
mkdir -p /home/fastdfs
new_fastdfs_ipaddr=$FASTDFS_IPADDR
old_fastdfs_ipaddr="192.168.0.165"
new_web_port=$WEB_PORT
old_web_port="8888"
echo 'FASTDFS_IPADDR=' ${new_fastdfs_ipaddr}
echo 'WEB_PORT=' ${new_web_port}
if [ "$new_fastdfs_ipaddr" = "" ];then
echo "[ERROR]The param FASTDFS_IPADDR is blank"
echo "[ERROR]you should declare this variable value. For example you should do like this 'docker run -d -e FASTDFS_IPADDR=your IP '"
exit
else
echo '[INFO] FASTDFS_IPADDR=' $FASTDFS_IPADDR
fi
if [ "$new_web_port" = "" ];then
echo "[WARN]the WEB_PORT default value is 8888"
else
echo "[INFO] the WEB_PORT value is "$WEB_PORT
fi
sed -i "s/$old_fastdfs_ipaddr/$new_fastdfs_ipaddr/g" /etc/fdfs/client.conf
sed -i "s/$old_fastdfs_ipaddr/$new_fastdfs_ipaddr/g" /etc/fdfs/storage.conf
sed -i "s/$old_fastdfs_ipaddr/$new_fastdfs_ipaddr/g" /etc/fdfs/mod_fastdfs.conf
sed -i "s/$old_web_port/$new_web_port/g" /usr/local/nginx/conf/nginx.conf
echo "start trackerd"
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart
echo "start storage"
/usr/bin/fdfs_storaged /etc/fdfs/storage.conf restart
echo "start nginx"
/usr/local/nginx/sbin/nginx
echo 'fastdfs start success........'
tail -f /dev/null
......@@ -218,17 +218,25 @@ while [ $RETRY_COUNT -lt $MAX_RETRY ]; do
echo "=============================================="
# --------------------------------
# 导入业务数据库(如果 databases/ 存在
# 导入业务数据库(如果 dmp/ 目录下存在 dmp 文件
# --------------------------------
IMPORT_SCRIPT_SRC="$SCRIPT_DIR/import_dm8_databases.sh"
if [[ -d "$SCRIPT_DIR/databases" ]] && ls "$SCRIPT_DIR/databases"/*_ddl.sql >/dev/null 2>&1; then
DMP_SCRIPT_SRC="$SCRIPT_DIR/import_dm_dmp.sh"
DMP_DIR="$SCRIPT_DIR/dmp"
if [[ -f "$DMP_SCRIPT_SRC" ]]; then
if [[ -d "$DMP_DIR" ]]; then
dmp_count=$(ls "$DMP_DIR"/*.dmp 2>/dev/null | wc -l)
if [[ $dmp_count -gt 0 ]]; then
echo ""
echo "[步骤7] 导入业务数据库表结构..."
if [[ -f "$IMPORT_SCRIPT_SRC" ]]; then
bash "$IMPORT_SCRIPT_SRC"
echo "[步骤7] 导入业务数据库(通过 dmp 文件)..."
bash "$DMP_SCRIPT_SRC" -d "$DMP_DIR"
else
echo "[WARN] 导入脚本不存在: $IMPORT_SCRIPT_SRC"
echo "[INFO] $DMP_DIR/ 下无 dmp 文件,跳过业务表导入"
fi
else
echo "[INFO] dmp/ 目录不存在 ($DMP_DIR),跳过业务表导入"
fi
else
echo "[WARN] dmp 导入脚本不存在: $DMP_SCRIPT_SRC,跳过业务表导入"
fi
exit 0
fi
......
......@@ -3,7 +3,7 @@
# 达梦数据库 dmp 文件导入脚本
#
# 功能:
# 1. 导入 DM_UBAINS.dmp 和 DM_NACOS.dmp 两个数据库
# 1. 导入 DM_UBAINS.dmp、DM_NACOS.dmp、DM_DEVOPS.dmp、DM_DEVOPS_VOICE.dmp、DM_OFFLINE.dmp
# 2. 导入后自动验证表、字段、数据完整性
#
# 使用方法:
......@@ -15,15 +15,19 @@
# -d, --dir dmp文件所在目录 (默认: /root)
# --skip-ubains 跳过 DM_UBAINS 导入
# --skip-nacos 跳过 DM_NACOS 导入
# --skip-devops 跳过 DM_DEVOPS 导入
# --skip-voice 跳过 DM_DEVOPS_VOICE 导入
# --skip-offline 跳过 DM_OFFLINE 导入
# --help 显示帮助信息
#
# 前置条件:
# 1. 达梦 Docker 容器已启动 (容器名: dm8-server)
# 2. dmp 文件已放置在指定目录
# 2. dmp 文件已放置在指定目录,文件命名规则为 {schema}.dmp
#
# 示例:
# ./import_dm_dmp.sh # 使用默认参数
# ./import_dm_dmp.sh -h 192.168.5.40 -d /root # 指定服务器和目录
# ./import_dm_dmp.sh --skip-nacos --skip-ubains # 只导入 DEVOPS/OFFLINE
# ============================================================================
set -e # 遇到错误立即退出
......@@ -33,17 +37,25 @@ set -e # 遇到错误立即退出
DM_HOST="192.168.5.40"
DM_PORT="5236"
DM_CONTAINER="dm8-server"
DM_INSTALL_DIR="/opt/dmdbms"
DM_INSTALL_DIR="/usr/local/dm8"
DMP_DIR="/root"
SYSDBA_PWD="Dameng2026Pwd"
SYSDBA_PWD="dNrprU&2S!"
# 用户密码(与导出时一致)
DM_UBAINS_PWD='dNrprU&2S!'
DM_NACOS_PWD='Nacos@2026Pwd'
DM_NACOS_PWD='dNrprU&2S!'
DM_DEVOPS_PWD='dNrprU&2S!'
DM_DEVOPS_VOICE_PWD='dNrprU&2S!'
DM_OFFLINE_PWD='dNrprU&2S!'
DM_HUAZHAO2_PWD='dNrprU&2S!'
# 导入选项
IMPORT_UBAINS=true
IMPORT_NACOS=true
IMPORT_DEVOPS=true
IMPORT_VOICE=true
IMPORT_OFFLINE=true
IMPORT_HUAZHAO2=true
# ======================== 参数解析 ========================
while [[ $# -gt 0 ]]; do
......@@ -68,6 +80,22 @@ while [[ $# -gt 0 ]]; do
IMPORT_NACOS=false
shift
;;
--skip-devops)
IMPORT_DEVOPS=false
shift
;;
--skip-voice)
IMPORT_VOICE=false
shift
;;
--skip-offline)
IMPORT_OFFLINE=false
shift
;;
--skip-huazhao2)
IMPORT_HUAZHAO2=false
shift
;;
--help)
echo "用法: $0 [选项]"
echo ""
......@@ -77,6 +105,10 @@ while [[ $# -gt 0 ]]; do
echo " -d, --dir dmp文件所在目录 (默认: $DMP_DIR)"
echo " --skip-ubains 跳过 DM_UBAINS 导入"
echo " --skip-nacos 跳过 DM_NACOS 导入"
echo " --skip-devops 跳过 DM_DEVOPS 导入"
echo " --skip-voice 跳过 DM_DEVOPS_VOICE 导入"
echo " --skip-offline 跳过 DM_OFFLINE 导入"
echo " --skip-huazhao2 跳过 DM_HUAZHAO2 导入"
echo " --help 显示帮助信息"
exit 0
;;
......@@ -113,9 +145,34 @@ log_error() {
}
# 执行 disql 命令(在容器内)
# 使用 heredoc 方式发送多行输入:先 SET DEFINE OFF 关闭替换变量,
# SET HEADING OFF 去掉表头,再执行 SQL,最后 EXIT。
# 容器内 bash 用 set +H 禁用历史扩展,避免 ! 触发历史替换。
exec_disql() {
local sql="$1"
docker exec -i $DM_CONTAINER bash -c "export LD_LIBRARY_PATH=$DM_INSTALL_DIR/bin && echo '$sql' | $DM_INSTALL_DIR/bin/disql SYSDBA/$SYSDBA_PWD@localhost:$DM_PORT" 2>&1
local conn_str="SYSDBA/${SYSDBA_PWD}@localhost:${DM_PORT}"
docker exec -i "$DM_CONTAINER" bash -c "set +H
export LD_LIBRARY_PATH=${DM_INSTALL_DIR}/bin
${DM_INSTALL_DIR}/bin/disql '${conn_str}' <<'DISQL_EOF'
SET DEFINE OFF
SET HEADING OFF
${sql}
EXIT
DISQL_EOF" 2>&1
}
# 执行 SQL 并返回纯数字结果(用于 SELECT COUNT 类查询)
# 取输出中最后一个数字(COUNT 结果通常在最后,排除执行号)
exec_disql_count() {
exec_disql "$1" 2>&1 | grep -v '执行号' | grep -oE '[0-9]+' | tail -1
}
# 执行含密码的 DDL(CREATE/ALTER USER)——直接调用 exec_disql,
# 由 SET DEFINE OFF 保护密码中的 & 不被 disql 解析为替换变量。
exec_disql_with_pwd() {
local ddl_prefix="$1" # 如 "CREATE USER DM_UBAINS IDENTIFIED BY "
local pwd="$2" # 原始密码(可能含 &)
exec_disql "${ddl_prefix}\"${pwd}\";"
}
# 导入 dmp 文件
......@@ -139,8 +196,9 @@ import_dmp() {
dmp_file="/tmp/${schema}.dmp"
fi
# 执行导入
docker exec $DM_CONTAINER bash -c "cd $DM_INSTALL_DIR/bin && ./dimp USERID=SYSDBA/$SYSDBA_PWD@localhost:$DM_PORT FILE=$dmp_file LOG=/tmp/${schema}_imp.log OWNER=$schema IGNORE=Y" 2>&1 | tail -20
# 执行导入(密码用单引号包裹,避免 & ! 被容器内 shell 解释)
# 不 tail 截断,实时输出完整日志便于观察进度
docker exec $DM_CONTAINER bash -c "cd $DM_INSTALL_DIR/bin && ./dimp USERID='SYSDBA/${SYSDBA_PWD}@localhost:${DM_PORT}' FILE=$dmp_file LOG=/tmp/${schema}_imp.log OWNER=$schema IGNORE=Y" 2>&1
if [ $? -eq 0 ]; then
log_success "$schema 导入完成"
......@@ -157,50 +215,48 @@ verify_ubains() {
local all_ok=true
# 1. 验证用户表数据
# 1. 验证核心表有数据(只检查行数 > 0,不验证精确行数)
log_info "验证核心表数据..."
local tables="
RMS_MANAGE_USER:1
SYS_ROLE:7
RMS_MANAGE_COMPANY:1
RMS_MANAGE_DEPARTMENT:19
SYS_CONFIGURATION:39
SYS_USER_ROLE:3
"
local tables="RMS_MANAGE_USER SYS_ROLE RMS_MANAGE_COMPANY RMS_MANAGE_DEPARTMENT SYS_CONFIGURATION SYS_USER_ROLE"
echo " 表名 实际行数 期望行数 状态"
echo " 表名 实际行数 状态"
echo " ----------------------------------------------------------"
while IFS=':' read -r table expected; do
# 跳过空行
[ -z "$table" ] && continue
# 查询实际行数
local result=$(exec_disql "SELECT COUNT(*) FROM DM_UBAINS.$table;" 2>&1)
local actual=$(echo "$result" | grep -oP '\d+' | tail -1)
if [ "$actual" == "$expected" ]; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) $(printf '%10s' $expected) ✓ OK"
for table in $tables; do
local actual=$(exec_disql_count "SELECT COUNT(*) FROM DM_UBAINS.$table;")
if [ -n "$actual" ] && [ "$actual" -gt 0 ] 2>/dev/null; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✓ OK"
else
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) $(printf '%10s' $expected) ✗ MISMATCH"
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✗ EMPTY"
all_ok=false
fi
done
# 2. 验证表数量(DM_UBAINS 应有 327 张表)
log_info "验证表数量..."
local table_count=$(exec_disql_count "SELECT COUNT(*) FROM DBA_TABLES WHERE OWNER='DM_UBAINS';")
echo " 表数量: $table_count (期望: 327)"
if [ "$table_count" -lt 320 ] 2>/dev/null; then
log_warn "表数量偏少!"
all_ok=false
fi
done <<< "$tables"
# 2. 验证序列数量
# 3. 验证序列数量(达梦 ALL_SEQUENCES 的列名为 SEQUENCE_OWNER)
log_info "验证序列..."
local seq_count=$(exec_disql "SELECT COUNT(*) FROM ALL_SEQUENCES WHERE OWNER='DM_UBAINS';" 2>&1 | grep -oP '\d+' | tail -1)
local seq_count=$(exec_disql_count "SELECT COUNT(*) FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER='DM_UBAINS';")
echo " 序列数量: $seq_count (期望: 232+)"
if [ "$seq_count" -lt 232 ] 2>/dev/null; then
log_warn "序列数量偏少!"
all_ok=false
fi
# 3. 验证小写字段数量
# 4. 验证小写字段数量(仅警告,不影响导入成功)
log_info "验证字段大小写..."
local lowercase_cols=$(exec_disql "SELECT COUNT(*) FROM ALL_TAB_COLUMNS WHERE OWNER='DM_UBAINS' AND COLUMN_NAME != UPPER(COLUMN_NAME);" 2>&1 | grep -oP '\d+' | tail -1)
local lowercase_cols=$(exec_disql_count "SELECT COUNT(*) FROM ALL_TAB_COLUMNS WHERE OWNER='DM_UBAINS' AND COLUMN_NAME != UPPER(COLUMN_NAME);")
echo " 小写字段: $lowercase_cols (期望: 0)"
if [ "$lowercase_cols" != "0" ]; then
log_warn "发现 $lowercase_cols 个小写字段!"
all_ok=false
if [ "$lowercase_cols" -gt 0 ] 2>/dev/null; then
log_warn "发现 $lowercase_cols 个小写字段(不影响导入,仅提示)"
fi
if $all_ok; then
......@@ -218,38 +274,23 @@ verify_nacos() {
local all_ok=true
# 验证表数据
local tables="
CONFIG_INFO:51
HIS_CONFIG_INFO:20
GROUP_CAPACITY:5
USERS:1
ROLES:1
CONFIG_INFO_GRAY:0
CONFIG_TAGS_RELATION:0
PERMISSIONS:0
TENANT_CAPACITY:0
TENANT_INFO:0
"
echo " 表名 实际行数 期望行数 状态"
echo " ----------------------------------------------------------"
# 验证核心表存在(只检查表是否有数据或存在,不验证精确行数)
log_info "验证核心表数据..."
while IFS=':' read -r table expected; do
# 跳过空行
[ -z "$table" ] && continue
local tables="CONFIG_INFO HIS_CONFIG_INFO GROUP_CAPACITY USERS ROLES"
# 查询实际行数
local result=$(exec_disql "SELECT COUNT(*) FROM DM_NACOS.$table;" 2>&1)
local actual=$(echo "$result" | grep -oP '\d+' | tail -1)
echo " 表名 实际行数 状态"
echo " ----------------------------------------------------------"
if [ "$actual" == "$expected" ]; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) $(printf '%10s' $expected) ✓ OK"
for table in $tables; do
local actual=$(exec_disql_count "SELECT COUNT(*) FROM DM_NACOS.$table;")
if [ -n "$actual" ]; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✓ OK"
else
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) $(printf '%10s' $expected) ✗ MISMATCH"
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✗ MISSING"
all_ok=false
fi
done <<< "$tables"
done
# 验证用户是否解锁(Nacos 启动需要)
log_info "验证用户状态..."
......@@ -273,6 +314,106 @@ verify_nacos() {
fi
}
# 验证 DM_DEVOPS 数据
verify_devops() {
log_info "验证 DM_DEVOPS 数据完整性..."
local all_ok=true
# 验证核心表有数据(只检查行数 > 0,不验证精确行数)
log_info "验证核心表数据..."
local tables="CMDB_COMPANY CMDB_ROOMFILE CMDB_ROOMUSER CMDB_DEVICEAUTH CMDB_FUNCTIONLIST CMDB_UPLOADIMAGE CMDB_UPLOADVOICE UPLAYER_MEDIAFILE"
echo " 表名 实际行数 状态"
echo " ----------------------------------------------------------"
for table in $tables; do
local actual=$(exec_disql_count "SELECT COUNT(*) FROM DM_DEVOPS.$table;")
if [ -n "$actual" ] && [ "$actual" -gt 0 ] 2>/dev/null; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✓ OK"
else
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✗ EMPTY"
all_ok=false
fi
done
# 验证表数量(DM_DEVOPS 应有 134 张表)
log_info "验证表数量..."
local table_count=$(exec_disql_count "SELECT COUNT(*) FROM DBA_TABLES WHERE OWNER='DM_DEVOPS';")
echo " 表数量: $table_count (期望: 134)"
if [ "$table_count" -lt 130 ] 2>/dev/null; then
log_warn "表数量偏少!"
all_ok=false
fi
if $all_ok; then
log_success "DM_DEVOPS 验证通过"
return 0
else
log_error "DM_DEVOPS 验证失败"
return 1
fi
}
# 验证 DM_DEVOPS_VOICE 数据
verify_voice() {
log_info "验证 DM_DEVOPS_VOICE 数据完整性..."
local all_ok=true
local table_count=$(exec_disql_count "SELECT COUNT(*) FROM DBA_TABLES WHERE OWNER='DM_DEVOPS_VOICE';")
echo " 表数量: $table_count"
if [ "$table_count" -gt 0 ]; then
log_success "DM_DEVOPS_VOICE 验证通过 (表数量: $table_count)"
return 0
else
log_error "DM_DEVOPS_VOICE 验证失败"
return 1
fi
}
# 验证 DM_OFFLINE 数据
verify_offline() {
log_info "验证 DM_OFFLINE 数据完整性..."
local all_ok=true
# 验证核心表存在(TB_HISTORY/TB_SENTENCE 可能为 0 行,只检查表存在)
log_info "验证核心表..."
local tables="TB_HISTORY TB_SENTENCE"
echo " 表名 实际行数 状态"
echo " ----------------------------------------------------------"
for table in $tables; do
local actual=$(exec_disql_count "SELECT COUNT(*) FROM DM_OFFLINE.$table;")
if [ -n "$actual" ]; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✓ OK"
else
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✗ MISSING"
all_ok=false
fi
done
# 验证索引
log_info "验证索引..."
local idx_count=$(exec_disql_count "SELECT COUNT(*) FROM DBA_INDEXES WHERE TABLE_OWNER='DM_OFFLINE' AND TABLE_NAME='tb_sentence';")
echo " tb_sentence 索引数量: $idx_count (期望: 2+, 含主键+history_id)"
if [ "$idx_count" -lt 2 ] 2>/dev/null; then
log_warn "索引数量偏少!"
all_ok=false
fi
if $all_ok; then
log_success "DM_OFFLINE 验证通过"
return 0
else
log_error "DM_OFFLINE 验证失败"
return 1
fi
}
# ======================== 主流程 ========================
echo "============================================================================"
......@@ -298,12 +439,16 @@ if $IMPORT_UBAINS; then
echo "导入 DM_UBAINS"
echo "============================================================================"
# 创建用户(如果不存在)
# 创建用户(如果不存在)- 密码含 & 用 CHR(38) 拼接避免 disql 替换变量
log_info "创建 DM_UBAINS 用户..."
exec_disql "CREATE USER DM_UBAINS IDENTIFIED BY \"$DM_UBAINS_PWD\";" 2>&1 || log_warn "用户可能已存在"
exec_disql_with_pwd "CREATE USER DM_UBAINS IDENTIFIED BY " "$DM_UBAINS_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_UBAINS"
# 导入后统一修改密码(避免 dmp 中原始密码与目标环境不一致)
log_info "统一 DM_UBAINS 用户密码..."
exec_disql_with_pwd "ALTER USER DM_UBAINS IDENTIFIED BY " "$DM_UBAINS_PWD" 2>&1
verify_ubains
fi
......@@ -314,9 +459,9 @@ if $IMPORT_NACOS; then
echo "导入 DM_NACOS"
echo "============================================================================"
# 创建用户(如果不存在)
# 创建用户(如果不存在)- 密码含 & 用 CHR(38) 拼接避免 disql 替换变量
log_info "创建 DM_NACOS 用户..."
exec_disql "CREATE USER DM_NACOS IDENTIFIED BY \"$DM_NACOS_PWD\";" 2>&1 || log_warn "用户可能已存在"
exec_disql_with_pwd "CREATE USER DM_NACOS IDENTIFIED BY " "$DM_NACOS_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_NACOS"
......@@ -324,19 +469,108 @@ if $IMPORT_NACOS; then
log_info "解锁 DM_NACOS 用户..."
exec_disql "ALTER USER DM_NACOS ACCOUNT UNLOCK;" 2>&1
# 导入后统一修改密码
log_info "统一 DM_NACOS 用户密码..."
exec_disql_with_pwd "ALTER USER DM_NACOS IDENTIFIED BY " "$DM_NACOS_PWD" 2>&1
verify_nacos
fi
# 导入 DM_DEVOPS
if $IMPORT_DEVOPS; then
echo ""
echo "============================================================================"
echo "导入 DM_DEVOPS (运维集控库)"
echo "============================================================================"
# 创建用户(如果不存在)- 密码含 & 用 CHR(38) 拼接避免 disql 替换变量
log_info "创建 DM_DEVOPS 用户..."
exec_disql_with_pwd "CREATE USER DM_DEVOPS IDENTIFIED BY " "$DM_DEVOPS_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_DEVOPS"
# 导入后统一修改密码
log_info "统一 DM_DEVOPS 用户密码..."
exec_disql_with_pwd "ALTER USER DM_DEVOPS IDENTIFIED BY " "$DM_DEVOPS_PWD" 2>&1
verify_devops
fi
# 导入 DM_DEVOPS_VOICE
if $IMPORT_VOICE; then
echo ""
echo "============================================================================"
echo "导入 DM_DEVOPS_VOICE (语音库)"
echo "============================================================================"
# 创建用户(如果不存在)- 密码含 & 用 CHR(38) 拼接避免 disql 替换变量
log_info "创建 DM_DEVOPS_VOICE 用户..."
exec_disql_with_pwd "CREATE USER DM_DEVOPS_VOICE IDENTIFIED BY " "$DM_DEVOPS_VOICE_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_DEVOPS_VOICE"
# 导入后统一修改密码
log_info "统一 DM_DEVOPS_VOICE 用户密码..."
exec_disql_with_pwd "ALTER USER DM_DEVOPS_VOICE IDENTIFIED BY " "$DM_DEVOPS_VOICE_PWD" 2>&1
verify_voice
fi
# 导入 DM_OFFLINE
if $IMPORT_OFFLINE; then
echo ""
echo "============================================================================"
echo "导入 DM_OFFLINE (离线库)"
echo "============================================================================"
# 创建用户(如果不存在)- 密码含 & 用 CHR(38) 拼接避免 disql 替换变量
log_info "创建 DM_OFFLINE 用户..."
exec_disql_with_pwd "CREATE USER DM_OFFLINE IDENTIFIED BY " "$DM_OFFLINE_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_OFFLINE"
# 导入后统一修改密码
log_info "统一 DM_OFFLINE 用户密码..."
exec_disql_with_pwd "ALTER USER DM_OFFLINE IDENTIFIED BY " "$DM_OFFLINE_PWD" 2>&1
verify_offline
fi
# 导入 DM_HUAZHAO2
if $IMPORT_HUAZHAO2; then
echo ""
echo "============================================================================"
echo "导入 DM_HUAZHAO2 (华兆库)"
echo "============================================================================"
# 创建用户(如果不存在)- 密码含 & 用 CHR(38) 拼接避免 disql 替换变量
log_info "创建 DM_HUAZHAO2 用户..."
exec_disql_with_pwd "CREATE USER DM_HUAZHAO2 IDENTIFIED BY " "$DM_HUAZHAO2_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_HUAZHAO2"
# 导入后统一修改密码
log_info "统一 DM_HUAZHAO2 用户密码..."
exec_disql_with_pwd "ALTER USER DM_HUAZHAO2 IDENTIFIED BY " "$DM_HUAZHAO2_PWD" 2>&1
log_success "DM_HUAZHAO2 导入完成"
fi
echo ""
echo "============================================================================"
echo "导入完成!"
echo "============================================================================"
echo ""
echo "注意事项:"
echo " 1. 如果 Nacos 启动报错 'account is locked',请执行:"
echo " 1. 所有业务用户密码已统一修改为: dNrprU&2S!"
echo ""
echo " 2. 如果 Nacos 启动报错 'account is locked',请执行:"
echo " docker exec $DM_CONTAINER $DM_INSTALL_DIR/bin/disql SYSDBA/$SYSDBA_PWD@localhost:$DM_PORT -e 'ALTER USER DM_NACOS ACCOUNT UNLOCK;'"
echo ""
echo " 2. 如果 Java 服务启动报 Hibernate 方言错误,请在 Nacos 配置中添加:"
echo " 3. 如果 Java 服务启动报 Hibernate 方言错误,请在 Nacos 配置中添加:"
echo " spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServerDialect"
echo ""
echo " 4. dmp 文件命名规则: {schema}.dmp (如 DM_UBAINS.dmp, DM_DEVOPS.dmp)"
echo " 文件需放在 $DMP_DIR 目录下"
echo ""
echo "============================================================================"
\ No newline at end of file
......@@ -312,28 +312,32 @@ setup_backup() {
#==============================================================#
# 函数: import_databases
# 功能: 如果 databases/ 目录下有 DDL SQL 文件,则导入业务表结构
# 功能: 通过 dmp 文件导入业务数据库
# 容器启动后,检查脚本同级目录下的 dmp/ 文件夹是否有 dmp 文件
#==============================================================#
IMPORT_SCRIPT_SRC="$DEPLOY_DIR/import_dm8_databases.sh"
DMP_SCRIPT_SRC="$DEPLOY_DIR/import_dm_dmp.sh"
DMP_DIR="$DEPLOY_DIR/dmp"
import_databases() {
if [[ ! -d "$DEPLOY_DIR/databases" ]]; then
log "INFO" "⏩ databases/ 目录不存在,跳过业务表导入"
if [[ ! -f "$DMP_SCRIPT_SRC" ]]; then
log "WARN" "⚠️ dmp 导入脚本不存在: $DMP_SCRIPT_SRC,跳过业务表导入"
return 0
fi
# 检查是否有 DDL SQL 文件
local ddl_count=$(ls "$DEPLOY_DIR/databases"/*_ddl.sql 2>/dev/null | wc -l)
if [[ $ddl_count -eq 0 ]]; then
log "INFO" "⏩ databases/ 下无 DDL SQL 文件,跳过业务表导入"
# 检查 dmp/ 目录下是否有 dmp 文件
if [[ ! -d "$DMP_DIR" ]]; then
log "INFO" "⏩ dmp/ 目录不存在 ($DMP_DIR),跳过业务表导入"
log "INFO" " 请创建 dmp/ 目录并放入 dmp 文件后手动执行: bash $DMP_SCRIPT_SRC -d $DMP_DIR"
return 0
fi
if [[ ! -f "$IMPORT_SCRIPT_SRC" ]]; then
log "WARN" "⚠️ 导入脚本不存在: $IMPORT_SCRIPT_SRC,跳过业务表导入"
local dmp_count=$(ls "$DMP_DIR"/*.dmp 2>/dev/null | wc -l)
if [[ $dmp_count -eq 0 ]]; then
log "INFO" "⏩ $DMP_DIR/ 下无 dmp 文件,跳过业务表导入"
log "INFO" " 请将 dmp 文件放入 $DMP_DIR/ 后手动执行: bash $DMP_SCRIPT_SRC -d $DMP_DIR"
return 0
fi
log "INFO" "📦 检测到 $ddl_count 个 DDL SQL 文件,开始导入业务表..."
# 调用导入脚本,传入容器名和密码
bash "$IMPORT_SCRIPT_SRC"
log "INFO" "✅ 业务导入完成"
log "INFO" "📦 检测到 $dmp_count 个 dmp 文件,开始导入业务数据库..."
# 调用 dmp 导入脚本,指定 dmp 目录
bash "$DMP_SCRIPT_SRC" -d "$DMP_DIR"
log "INFO" "✅ 业务数据库导入完成"
return 0
}
......
#!/bin/bash
# ============================================================================
# 达梦数据库 dmp 文件导入脚本
# 达梦数据库 dmp 文件导入脚本(X86 架构)
#
# 功能:
# 1. 导入 DM_UBAINS.dmp 和 DM_NACOS.dmp 两个数据库
# 1. 导入 DM_UBAINS.dmp、DM_NACOS.dmp、DM_DEVOPS.dmp、DM_DEVOPS_VOICE.dmp、DM_OFFLINE.dmp、DM_HUAZHAO2.dmp
# 2. 导入后自动验证表、字段、数据完整性
#
# 使用方法:
......@@ -15,21 +15,26 @@
# -d, --dir dmp文件所在目录 (默认: /root)
# --skip-ubains 跳过 DM_UBAINS 导入
# --skip-nacos 跳过 DM_NACOS 导入
# --skip-devops 跳过 DM_DEVOPS 导入
# --skip-voice 跳过 DM_DEVOPS_VOICE 导入
# --skip-offline 跳过 DM_OFFLINE 导入
# --skip-huazhao2 跳过 DM_HUAZHAO2 导入
# --help 显示帮助信息
#
# 前置条件:
# 1. 达梦 Docker 容器已启动 (容器名: dm8-server)
# 2. dmp 文件已放置在指定目录
# 2. dmp 文件已放置在指定目录,文件命名规则为 {schema}.dmp
#
# 示例:
# ./import_dm_dmp.sh # 使用默认参数
# ./import_dm_dmp.sh -h 192.168.5.40 -d /root # 指定服务器和目录
# ./import_dm_dmp.sh --skip-nacos --skip-ubains # 只导入 DEVOPS/OFFLINE
# ============================================================================
set -e # 遇到错误立即退出
# ======================== 配置区 ========================
# 默认配置
# 默认配置(X86 架构:安装目录 /opt/dmdbms,SYSDBA 密码 Dameng2026Pwd)
DM_HOST="192.168.5.40"
DM_PORT="5236"
DM_CONTAINER="dm8-server"
......@@ -37,13 +42,21 @@ DM_INSTALL_DIR="/opt/dmdbms"
DMP_DIR="/root"
SYSDBA_PWD="Dameng2026Pwd"
# 用户密码(与导出时一致
# 用户密码(统一为 dNrprU&2S!
DM_UBAINS_PWD='dNrprU&2S!'
DM_NACOS_PWD='Nacos@2026Pwd'
DM_NACOS_PWD='dNrprU&2S!'
DM_DEVOPS_PWD='dNrprU&2S!'
DM_DEVOPS_VOICE_PWD='dNrprU&2S!'
DM_OFFLINE_PWD='dNrprU&2S!'
DM_HUAZHAO2_PWD='dNrprU&2S!'
# 导入选项
IMPORT_UBAINS=true
IMPORT_NACOS=true
IMPORT_DEVOPS=true
IMPORT_VOICE=true
IMPORT_OFFLINE=true
IMPORT_HUAZHAO2=true
# ======================== 参数解析 ========================
while [[ $# -gt 0 ]]; do
......@@ -68,6 +81,22 @@ while [[ $# -gt 0 ]]; do
IMPORT_NACOS=false
shift
;;
--skip-devops)
IMPORT_DEVOPS=false
shift
;;
--skip-voice)
IMPORT_VOICE=false
shift
;;
--skip-offline)
IMPORT_OFFLINE=false
shift
;;
--skip-huazhao2)
IMPORT_HUAZHAO2=false
shift
;;
--help)
echo "用法: $0 [选项]"
echo ""
......@@ -77,6 +106,10 @@ while [[ $# -gt 0 ]]; do
echo " -d, --dir dmp文件所在目录 (默认: $DMP_DIR)"
echo " --skip-ubains 跳过 DM_UBAINS 导入"
echo " --skip-nacos 跳过 DM_NACOS 导入"
echo " --skip-devops 跳过 DM_DEVOPS 导入"
echo " --skip-voice 跳过 DM_DEVOPS_VOICE 导入"
echo " --skip-offline 跳过 DM_OFFLINE 导入"
echo " --skip-huazhao2 跳过 DM_HUAZHAO2 导入"
echo " --help 显示帮助信息"
exit 0
;;
......@@ -113,9 +146,34 @@ log_error() {
}
# 执行 disql 命令(在容器内)
# 使用 heredoc 方式发送多行输入:先 SET DEFINE OFF 关闭替换变量,
# SET HEADING OFF 去掉表头,再执行 SQL,最后 EXIT。
# 容器内 bash 用 set +H 禁用历史扩展,避免 ! 触发历史替换。
exec_disql() {
local sql="$1"
docker exec -i $DM_CONTAINER bash -c "export LD_LIBRARY_PATH=$DM_INSTALL_DIR/bin && echo '$sql' | $DM_INSTALL_DIR/bin/disql SYSDBA/$SYSDBA_PWD@localhost:$DM_PORT" 2>&1
local conn_str="SYSDBA/${SYSDBA_PWD}@localhost:${DM_PORT}"
docker exec -i "$DM_CONTAINER" bash -c "set +H
export LD_LIBRARY_PATH=${DM_INSTALL_DIR}/bin
${DM_INSTALL_DIR}/bin/disql '${conn_str}' <<'DISQL_EOF'
SET DEFINE OFF
SET HEADING OFF
${sql}
EXIT
DISQL_EOF" 2>&1
}
# 执行 SQL 并返回纯数字结果(用于 SELECT COUNT 类查询)
# 取输出中最后一个数字(COUNT 结果通常在最后,排除执行号)
exec_disql_count() {
exec_disql "$1" 2>&1 | grep -v '执行号' | grep -oE '[0-9]+' | tail -1
}
# 执行含密码的 DDL(CREATE/ALTER USER)——直接调用 exec_disql,
# 由 SET DEFINE OFF 保护密码中的 & 不被 disql 解析为替换变量。
exec_disql_with_pwd() {
local ddl_prefix="$1" # 如 "CREATE USER DM_UBAINS IDENTIFIED BY "
local pwd="$2" # 原始密码(可能含 &)
exec_disql "${ddl_prefix}\"${pwd}\";"
}
# 导入 dmp 文件
......@@ -139,8 +197,9 @@ import_dmp() {
dmp_file="/tmp/${schema}.dmp"
fi
# 执行导入
docker exec $DM_CONTAINER bash -c "cd $DM_INSTALL_DIR/bin && ./dimp USERID=SYSDBA/$SYSDBA_PWD@localhost:$DM_PORT FILE=$dmp_file LOG=/tmp/${schema}_imp.log OWNER=$schema IGNORE=Y" 2>&1 | tail -20
# 执行导入(密码用单引号包裹,避免 & ! 被容器内 shell 解释)
# 不 tail 截断,实时输出完整日志便于观察进度
docker exec $DM_CONTAINER bash -c "cd $DM_INSTALL_DIR/bin && ./dimp USERID='SYSDBA/${SYSDBA_PWD}@localhost:${DM_PORT}' FILE=$dmp_file LOG=/tmp/${schema}_imp.log OWNER=$schema IGNORE=Y" 2>&1
if [ $? -eq 0 ]; then
log_success "$schema 导入完成"
......@@ -157,50 +216,48 @@ verify_ubains() {
local all_ok=true
# 1. 验证用户表数据
# 1. 验证核心表有数据(只检查行数 > 0,不验证精确行数)
log_info "验证核心表数据..."
local tables="
RMS_MANAGE_USER:1
SYS_ROLE:7
RMS_MANAGE_COMPANY:1
RMS_MANAGE_DEPARTMENT:19
SYS_CONFIGURATION:39
SYS_USER_ROLE:3
"
local tables="RMS_MANAGE_USER SYS_ROLE RMS_MANAGE_COMPANY RMS_MANAGE_DEPARTMENT SYS_CONFIGURATION SYS_USER_ROLE"
echo " 表名 实际行数 期望行数 状态"
echo " 表名 实际行数 状态"
echo " ----------------------------------------------------------"
while IFS=':' read -r table expected; do
# 跳过空行
[ -z "$table" ] && continue
# 查询实际行数
local result=$(exec_disql "SELECT COUNT(*) FROM DM_UBAINS.$table;" 2>&1)
local actual=$(echo "$result" | grep -oP '\d+' | tail -1)
if [ "$actual" == "$expected" ]; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) $(printf '%10s' $expected) ✓ OK"
for table in $tables; do
local actual=$(exec_disql_count "SELECT COUNT(*) FROM DM_UBAINS.$table;")
if [ -n "$actual" ] && [ "$actual" -gt 0 ] 2>/dev/null; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✓ OK"
else
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) $(printf '%10s' $expected) ✗ MISMATCH"
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✗ EMPTY"
all_ok=false
fi
done
# 2. 验证表数量(DM_UBAINS 应有 327 张表)
log_info "验证表数量..."
local table_count=$(exec_disql_count "SELECT COUNT(*) FROM DBA_TABLES WHERE OWNER='DM_UBAINS';")
echo " 表数量: $table_count (期望: 327)"
if [ "$table_count" -lt 320 ] 2>/dev/null; then
log_warn "表数量偏少!"
all_ok=false
fi
done <<< "$tables"
# 2. 验证序列数量
# 3. 验证序列数量(达梦 ALL_SEQUENCES 的列名为 SEQUENCE_OWNER)
log_info "验证序列..."
local seq_count=$(exec_disql "SELECT COUNT(*) FROM ALL_SEQUENCES WHERE OWNER='DM_UBAINS';" 2>&1 | grep -oP '\d+' | tail -1)
local seq_count=$(exec_disql_count "SELECT COUNT(*) FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER='DM_UBAINS';")
echo " 序列数量: $seq_count (期望: 232+)"
if [ "$seq_count" -lt 232 ] 2>/dev/null; then
log_warn "序列数量偏少!"
all_ok=false
fi
# 3. 验证小写字段数量
# 4. 验证小写字段数量(仅警告,不影响导入成功)
log_info "验证字段大小写..."
local lowercase_cols=$(exec_disql "SELECT COUNT(*) FROM ALL_TAB_COLUMNS WHERE OWNER='DM_UBAINS' AND COLUMN_NAME != UPPER(COLUMN_NAME);" 2>&1 | grep -oP '\d+' | tail -1)
local lowercase_cols=$(exec_disql_count "SELECT COUNT(*) FROM ALL_TAB_COLUMNS WHERE OWNER='DM_UBAINS' AND COLUMN_NAME != UPPER(COLUMN_NAME);")
echo " 小写字段: $lowercase_cols (期望: 0)"
if [ "$lowercase_cols" != "0" ]; then
log_warn "发现 $lowercase_cols 个小写字段!"
all_ok=false
if [ "$lowercase_cols" -gt 0 ] 2>/dev/null; then
log_warn "发现 $lowercase_cols 个小写字段(不影响导入,仅提示)"
fi
if $all_ok; then
......@@ -218,38 +275,23 @@ verify_nacos() {
local all_ok=true
# 验证表数据
local tables="
CONFIG_INFO:51
HIS_CONFIG_INFO:20
GROUP_CAPACITY:5
USERS:1
ROLES:1
CONFIG_INFO_GRAY:0
CONFIG_TAGS_RELATION:0
PERMISSIONS:0
TENANT_CAPACITY:0
TENANT_INFO:0
"
echo " 表名 实际行数 期望行数 状态"
echo " ----------------------------------------------------------"
# 验证核心表存在(只检查表是否有数据或存在,不验证精确行数)
log_info "验证核心表数据..."
while IFS=':' read -r table expected; do
# 跳过空行
[ -z "$table" ] && continue
local tables="CONFIG_INFO HIS_CONFIG_INFO GROUP_CAPACITY USERS ROLES"
# 查询实际行数
local result=$(exec_disql "SELECT COUNT(*) FROM DM_NACOS.$table;" 2>&1)
local actual=$(echo "$result" | grep -oP '\d+' | tail -1)
echo " 表名 实际行数 状态"
echo " ----------------------------------------------------------"
if [ "$actual" == "$expected" ]; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) $(printf '%10s' $expected) ✓ OK"
for table in $tables; do
local actual=$(exec_disql_count "SELECT COUNT(*) FROM DM_NACOS.$table;")
if [ -n "$actual" ]; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✓ OK"
else
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) $(printf '%10s' $expected) ✗ MISMATCH"
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✗ MISSING"
all_ok=false
fi
done <<< "$tables"
done
# 验证用户是否解锁(Nacos 启动需要)
log_info "验证用户状态..."
......@@ -273,6 +315,106 @@ verify_nacos() {
fi
}
# 验证 DM_DEVOPS 数据
verify_devops() {
log_info "验证 DM_DEVOPS 数据完整性..."
local all_ok=true
# 验证核心表有数据(只检查行数 > 0,不验证精确行数)
log_info "验证核心表数据..."
local tables="CMDB_COMPANY CMDB_ROOMFILE CMDB_ROOMUSER CMDB_DEVICEAUTH CMDB_FUNCTIONLIST CMDB_UPLOADIMAGE CMDB_UPLOADVOICE UPLAYER_MEDIAFILE"
echo " 表名 实际行数 状态"
echo " ----------------------------------------------------------"
for table in $tables; do
local actual=$(exec_disql_count "SELECT COUNT(*) FROM DM_DEVOPS.$table;")
if [ -n "$actual" ] && [ "$actual" -gt 0 ] 2>/dev/null; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✓ OK"
else
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✗ EMPTY"
all_ok=false
fi
done
# 验证表数量(DM_DEVOPS 应有 134 张表)
log_info "验证表数量..."
local table_count=$(exec_disql_count "SELECT COUNT(*) FROM DBA_TABLES WHERE OWNER='DM_DEVOPS';")
echo " 表数量: $table_count (期望: 134)"
if [ "$table_count" -lt 130 ] 2>/dev/null; then
log_warn "表数量偏少!"
all_ok=false
fi
if $all_ok; then
log_success "DM_DEVOPS 验证通过"
return 0
else
log_error "DM_DEVOPS 验证失败"
return 1
fi
}
# 验证 DM_DEVOPS_VOICE 数据
verify_voice() {
log_info "验证 DM_DEVOPS_VOICE 数据完整性..."
local all_ok=true
local table_count=$(exec_disql_count "SELECT COUNT(*) FROM DBA_TABLES WHERE OWNER='DM_DEVOPS_VOICE';")
echo " 表数量: $table_count"
if [ "$table_count" -gt 0 ]; then
log_success "DM_DEVOPS_VOICE 验证通过 (表数量: $table_count)"
return 0
else
log_error "DM_DEVOPS_VOICE 验证失败"
return 1
fi
}
# 验证 DM_OFFLINE 数据
verify_offline() {
log_info "验证 DM_OFFLINE 数据完整性..."
local all_ok=true
# 验证核心表存在(TB_HISTORY/TB_SENTENCE 可能为 0 行,只检查表存在)
log_info "验证核心表..."
local tables="TB_HISTORY TB_SENTENCE"
echo " 表名 实际行数 状态"
echo " ----------------------------------------------------------"
for table in $tables; do
local actual=$(exec_disql_count "SELECT COUNT(*) FROM DM_OFFLINE.$table;")
if [ -n "$actual" ]; then
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✓ OK"
else
echo " $(printf '%-30s' $table) $(printf '%10s' $actual) ✗ MISSING"
all_ok=false
fi
done
# 验证索引
log_info "验证索引..."
local idx_count=$(exec_disql_count "SELECT COUNT(*) FROM DBA_INDEXES WHERE TABLE_OWNER='DM_OFFLINE' AND TABLE_NAME='tb_sentence';")
echo " tb_sentence 索引数量: $idx_count (期望: 2+, 含主键+history_id)"
if [ "$idx_count" -lt 2 ] 2>/dev/null; then
log_warn "索引数量偏少!"
all_ok=false
fi
if $all_ok; then
log_success "DM_OFFLINE 验证通过"
return 0
else
log_error "DM_OFFLINE 验证失败"
return 1
fi
}
# ======================== 主流程 ========================
echo "============================================================================"
......@@ -298,12 +440,16 @@ if $IMPORT_UBAINS; then
echo "导入 DM_UBAINS"
echo "============================================================================"
# 创建用户(如果不存在)
# 创建用户(如果不存在)- 密码含 & 用 exec_disql_with_pwd 处理,SET DEFINE OFF 避免替换变量
log_info "创建 DM_UBAINS 用户..."
exec_disql "CREATE USER DM_UBAINS IDENTIFIED BY \"$DM_UBAINS_PWD\";" 2>&1 || log_warn "用户可能已存在"
exec_disql_with_pwd "CREATE USER DM_UBAINS IDENTIFIED BY " "$DM_UBAINS_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_UBAINS"
# 导入后统一修改密码(避免 dmp 中原始密码与目标环境不一致)
log_info "统一 DM_UBAINS 用户密码..."
exec_disql_with_pwd "ALTER USER DM_UBAINS IDENTIFIED BY " "$DM_UBAINS_PWD" 2>&1
verify_ubains
fi
......@@ -316,7 +462,7 @@ if $IMPORT_NACOS; then
# 创建用户(如果不存在)
log_info "创建 DM_NACOS 用户..."
exec_disql "CREATE USER DM_NACOS IDENTIFIED BY \"$DM_NACOS_PWD\";" 2>&1 || log_warn "用户可能已存在"
exec_disql_with_pwd "CREATE USER DM_NACOS IDENTIFIED BY " "$DM_NACOS_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_NACOS"
......@@ -324,19 +470,108 @@ if $IMPORT_NACOS; then
log_info "解锁 DM_NACOS 用户..."
exec_disql "ALTER USER DM_NACOS ACCOUNT UNLOCK;" 2>&1
# 导入后统一修改密码
log_info "统一 DM_NACOS 用户密码..."
exec_disql_with_pwd "ALTER USER DM_NACOS IDENTIFIED BY " "$DM_NACOS_PWD" 2>&1
verify_nacos
fi
# 导入 DM_DEVOPS
if $IMPORT_DEVOPS; then
echo ""
echo "============================================================================"
echo "导入 DM_DEVOPS (运维集控库)"
echo "============================================================================"
# 创建用户(如果不存在)
log_info "创建 DM_DEVOPS 用户..."
exec_disql_with_pwd "CREATE USER DM_DEVOPS IDENTIFIED BY " "$DM_DEVOPS_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_DEVOPS"
# 导入后统一修改密码
log_info "统一 DM_DEVOPS 用户密码..."
exec_disql_with_pwd "ALTER USER DM_DEVOPS IDENTIFIED BY " "$DM_DEVOPS_PWD" 2>&1
verify_devops
fi
# 导入 DM_DEVOPS_VOICE
if $IMPORT_VOICE; then
echo ""
echo "============================================================================"
echo "导入 DM_DEVOPS_VOICE (语音库)"
echo "============================================================================"
# 创建用户(如果不存在)
log_info "创建 DM_DEVOPS_VOICE 用户..."
exec_disql_with_pwd "CREATE USER DM_DEVOPS_VOICE IDENTIFIED BY " "$DM_DEVOPS_VOICE_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_DEVOPS_VOICE"
# 导入后统一修改密码
log_info "统一 DM_DEVOPS_VOICE 用户密码..."
exec_disql_with_pwd "ALTER USER DM_DEVOPS_VOICE IDENTIFIED BY " "$DM_DEVOPS_VOICE_PWD" 2>&1
verify_voice
fi
# 导入 DM_OFFLINE
if $IMPORT_OFFLINE; then
echo ""
echo "============================================================================"
echo "导入 DM_OFFLINE (离线库)"
echo "============================================================================"
# 创建用户(如果不存在)
log_info "创建 DM_OFFLINE 用户..."
exec_disql_with_pwd "CREATE USER DM_OFFLINE IDENTIFIED BY " "$DM_OFFLINE_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_OFFLINE"
# 导入后统一修改密码
log_info "统一 DM_OFFLINE 用户密码..."
exec_disql_with_pwd "ALTER USER DM_OFFLINE IDENTIFIED BY " "$DM_OFFLINE_PWD" 2>&1
verify_offline
fi
# 导入 DM_HUAZHAO2
if $IMPORT_HUAZHAO2; then
echo ""
echo "============================================================================"
echo "导入 DM_HUAZHAO2 (华兆库)"
echo "============================================================================"
# 创建用户(如果不存在)
log_info "创建 DM_HUAZHAO2 用户..."
exec_disql_with_pwd "CREATE USER DM_HUAZHAO2 IDENTIFIED BY " "$DM_HUAZHAO2_PWD" 2>&1 || log_warn "用户可能已存在"
import_dmp "DM_HUAZHAO2"
# 导入后统一修改密码
log_info "统一 DM_HUAZHAO2 用户密码..."
exec_disql_with_pwd "ALTER USER DM_HUAZHAO2 IDENTIFIED BY " "$DM_HUAZHAO2_PWD" 2>&1
log_success "DM_HUAZHAO2 导入完成"
fi
echo ""
echo "============================================================================"
echo "导入完成!"
echo "============================================================================"
echo ""
echo "注意事项:"
echo " 1. 如果 Nacos 启动报错 'account is locked',请执行:"
echo " 1. 所有业务用户密码已统一修改为: dNrprU&2S!"
echo ""
echo " 2. 如果 Nacos 启动报错 'account is locked',请执行:"
echo " docker exec $DM_CONTAINER $DM_INSTALL_DIR/bin/disql SYSDBA/$SYSDBA_PWD@localhost:$DM_PORT -e 'ALTER USER DM_NACOS ACCOUNT UNLOCK;'"
echo ""
echo " 2. 如果 Java 服务启动报 Hibernate 方言错误,请在 Nacos 配置中添加:"
echo " 3. 如果 Java 服务启动报 Hibernate 方言错误,请在 Nacos 配置中添加:"
echo " spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServerDialect"
echo ""
echo " 4. dmp 文件命名规则: {schema}.dmp (如 DM_UBAINS.dmp, DM_DEVOPS.dmp)"
echo " 文件需放在 $DMP_DIR 目录下"
echo ""
echo "============================================================================"
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论