提交 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 -*-
"""
执行默认值修复 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
echo ""
echo "[步骤7] 导入业务数据库表结构..."
if [[ -f "$IMPORT_SCRIPT_SRC" ]]; then
bash "$IMPORT_SCRIPT_SRC"
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] 导入业务数据库(通过 dmp 文件)..."
bash "$DMP_SCRIPT_SRC" -d "$DMP_DIR"
else
echo "[INFO] $DMP_DIR/ 下无 dmp 文件,跳过业务表导入"
fi
else
echo "[WARN] 导入脚本不存在: $IMPORT_SCRIPT_SRC"
echo "[INFO] dmp/ 目录不存在 ($DMP_DIR),跳过业务表导入"
fi
else
echo "[WARN] dmp 导入脚本不存在: $DMP_SCRIPT_SRC,跳过业务表导入"
fi
exit 0
fi
......
......@@ -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
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论