auto: sync OpenClaw config 2026-08-14 17:17

This commit is contained in:
2026-08-14 17:17:05 +08:00
parent d6da0db3d4
commit 7c4fe5a371
28 changed files with 2774 additions and 5 deletions
+3
View File
@@ -26,6 +26,9 @@
"id": "deepseek-v4-flash",
"name": "DeepSeek V4 Flash",
"reasoning": false,
"compat": {
"supportsTools": false
},
"input": [
"text"
],
@@ -1 +1 @@
{"o9cq80w6ZnnPGItW-GGACkwd69bA@im.wechat":"AARzJWAFAAABAAAAAACA0AYD7qF6+TdJp1d+aiAAAAB+9905Q6UiugPBawU3n3cyzQX+LkN8ofRzsCZYN0mt7nF+0FcQj5o0uzppZISabe/CvWlZU9s36xGpntNVSV4HppHXHqec"}
{"o9cq80w6ZnnPGItW-GGACkwd69bA@im.wechat":"AARzJWAFAAABAAAAAABSELVdc9tChDo+RKl+aiAAAAB+9905Q6UiugPBawU3n3cyzQX+LkN8ofRzsCZYN0mt7mzlUWDKY0/s0LdFjda3pL5Ubkl2FkeKOMbXRnAbzhPUHGWXIMUw"}
@@ -1 +1 @@
{"get_updates_buf":"ChAIVBDczZns/zMYmp6chfYzEjplNDhhY2U5ODE0ZjBAaW0uYm90OjA2MDAwMGQ0ZmYwNzg5NWY2NDFlMmZjNmIzNGUzMzNkZDIwMjgx"}
{"get_updates_buf":"ChAIVxCF65T2/zMYmp6chfYzEjplNDhhY2U5ODE0ZjBAaW0uYm90OjA2MDAwMGQ0ZmYwNzg5NWY2NDFlMmZjNmIzNGUzMzNkZDIwMjgx"}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,166 @@
---
name: "mysql-schema-sync"
description: "MySQL 表结构同步最佳实践:跨环境同步 DDL,处理 CREATE/ALTER 失败场景"
status: proposal
version: "v1"
date: "2026-08-14T07:14:36.432Z"
---
# MySQL 表结构同步最佳实践
## 场景
将准生产/测试环境的表结构同步到 VPS/生产环境,确保 DDL 一致。
## 核心原则
1. **表名必须带数据库前缀** - 如 `dmp_smdm.dmp_md_item_info`,因为生产实例有多个库
2. **不暴露敏感信息** - 脚本中只写库名和表名,IP/密码用环境变量
3. **先验证后执行** - 先 SELECT 确认表结构和数据量
4. **CREATE 优先于 ALTER** - 能新建不修改,减少冲突
## 脚本生成规范
### 1. 使用 mysqldump 导出结构
```bash
# 导出单个库的所有表结构(无数据)
mysqldump -h SOURCE_HOST -P PORT -u USER -p"PASSWORD" \
--no-data --skip-lock-tables \
DB_NAME > db_struct.sql
```
### 2. 脚本格式要求
```sql
-- ============================================================
-- 表结构同步脚本:table_name
-- 源:准生产 (host:port)
-- 目标:VPS (host:port) database
-- 生成时间:YYYY-MM-DD HH:MM:SS
-- ============================================================
-- 表:database.table_name
CREATE TABLE IF NOT EXISTS `database`.`table_name` (
-- 字段定义
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='表说明';
-- 验证
-- SHOW CREATE TABLE database.table_name\G
```
### 3. 目录结构
```
xuan-sql/scripts/YYYYMMDD/
├── README.md # 同步记录(成功/失败清单)
├── database__table__struct.sql # 单表脚本
└── run_sync.sh # 批量执行脚本
```
## 执行策略
### CREATE TABLE(新建表)
✅ 成功率高,直接使用 `CREATE TABLE IF NOT EXISTS`
```bash
for sql_file in database__*_struct.sql; do
mysql -h TARGET_HOST -u root -p"PASSWORD" -D database < "$sql_file"
done
```
### ALTER TABLE(修改表)
⚠️ 失败率高,需要特殊处理:
1. **先检查目标表是否存在**
```sql
SELECT TABLE_NAME FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'database' AND TABLE_NAME = 'table_name';
```
2. **对比结构差异**
```sql
-- 源表结构
SHOW CREATE TABLE source.table_name\G
-- 目标表结构
SHOW CREATE TABLE target.table_name\G
```
3. **生成差异脚本**(只修改不同的字段/索引)
4. **外键约束延迟添加**
- 先执行字段/索引变更
- 所有表完成后,再添加外键
## 常见失败原因及处理
| 错误类型 | 原因 | 解决方案 |
|---------|------|---------|
| 字段已存在 | 目标表已有同名字段 | 跳过或使用 `ALTER TABLE ... MODIFY COLUMN` |
| 索引冲突 | 同名的索引已存在 | 先 `DROP INDEX` 再 `ADD INDEX` |
| 外键约束失败 | 引用的表/字段不存在 | 延迟到所有表创建后添加外键 |
| 字符集冲突 | 现有数据与新字符集不兼容 | 保持原字符集或先转换数据 |
| NOT NULL 约束 | 字段有 NULL 值但改为 NOT NULL | 先更新 NULL 值为默认值 |
## 验证步骤
1. **表数量对比**
```sql
SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'database';
```
2. **结构对比**
```sql
SHOW CREATE TABLE database.table_name\G
```
3. **关键字段验证**
```sql
SELECT COLUMN_NAME, IS_NULLABLE, COLUMN_DEFAULT, DATA_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'database' AND TABLE_NAME = 'table_name';
```
## 回滚方案
1. **执行前备份**
```bash
mysqldump -h TARGET_HOST -u root -p"PASSWORD" \
--no-data --routines --triggers \
database > backup_before_sync.sql
```
2. **记录执行日志**
```bash
./run_sync.sh > sync_log.txt 2>&1
```
3. **失败表单独处理**
- 记录失败的表和错误信息
- 人工分析后生成修复脚本
## 示例:dmp_smdm 同步(2026-08-14
**源:** 准生产 `100.115.195.188:50036`
**目标:** VPS `101.34.227.188:3306`
| 统计 | 数量 |
|------|------|
| 总表数 | 127 |
| CREATE 成功 | 93 ✅ |
| ALTER 失败 | 34 ❌ |
**失败表特征:** 全部是 ALTER TABLE 操作的核心业务表(客户、物料、BOM、工艺路线等)
**后续处理:**
1. 人工对比失败表的源/目标结构差异
2. 生成针对性的 ALTER 脚本(只修改必要字段)
3. 在业务低峰期执行变更
## 相关技能
- sql-toolkit: SQL 查询、设计、迁移
- mysql-ddl-backup: 定时备份表结构
@@ -0,0 +1 @@
{"schema":"openclaw.skill-workshop.proposal.v1","id":"mysql-schema-sync-20260814-ae77f6b8af","kind":"create","status":"applied","title":"Create mysql-schema-sync","description":"MySQL 表结构同步最佳实践:跨环境同步 DDL,处理 CREATE/ALTER 失败场景","createdAt":"2026-08-14T07:14:36.432Z","updatedAt":"2026-08-14T07:14:46.611Z","createdBy":"skill-workshop","origin":{"agentId":"sql","sessionKey":"agent:sql:main","runId":"0faa8e48-f62f-47a5-8287-ea9b79e0b9c2","messageId":"0faa8e48-f62f-47a5-8287-ea9b79e0b9c2"},"proposedVersion":"v1","draftFile":"PROPOSAL.md","draftHash":"12787d9dc06dc79df3d4b3a043b9fcadac8f8bd86fc794055af161af380a09ef","target":{"skillName":"mysql-schema-sync","skillKey":"mysql-schema-sync","skillDir":"/home/yangxuan/.openclaw/workspace-sql/skills/mysql-schema-sync","skillFile":"/home/yangxuan/.openclaw/workspace-sql/skills/mysql-schema-sync/SKILL.md","source":"openclaw-workspace"},"scan":{"state":"clean","scannedAt":"2026-08-14T07:14:46.514Z","critical":0,"warn":0,"info":0,"findings":[]},"appliedAt":"2026-08-14T07:14:46.611Z"}
@@ -0,0 +1 @@
{"schema":"openclaw.skill-workshop.rollback.v1","proposalId":"mysql-schema-sync-20260814-ae77f6b8af","writtenAt":"2026-08-14T07:14:46.521Z","targetSkillFile":"/home/yangxuan/.openclaw/workspace-sql/skills/mysql-schema-sync/SKILL.md","action":"create"}
@@ -0,0 +1,135 @@
---
name: "table-alias"
description: "将准生产数据库表结构同步到 VPS 开发库,智能对比差异并执行 ALTER/CREATE"
status: proposal
version: "v1"
date: "2026-08-14T06:24:41.780Z"
---
# table-alias - 表结构对齐技能
## 功能描述
将**准生产数据库**的表结构同步到**VPS 开发库**,确保开发环境与准生产环境表结构一致。
**同步方向:** 准生产(只读)→ VPS(root 可写)
## 触发方式
**自然语言命令:**
```
同步准生产 dmp_serp 的表结构到 VPS
同步准生产 dmp_smdm 的表结构到 VPS
```
## 执行流程
1. 解析命令提取源库名和目标库名
2. 从配置文件读取连接信息
3. 验证源库(只读)和目标库(root)连接
4. 获取源数据库所有表列表
5. 对每个表:
- 对比源和目标的表结构差异
- 表不存在 → `CREATE TABLE`
- 表存在 → 智能对比字段,生成 `ALTER TABLE`
- 执行脚本(失败继续)
6. 生成 SQL 脚本文件和 README
7. 记录执行日志到 `memory/{date}.md`
## 智能对比
**对比维度:**
- 表是否存在 → `SHOW TABLES`
- 字段列表 → `information_schema.COLUMNS`
- 字段类型、NULL 约束、默认值、注释
- 索引 → `information_schema.STATISTICS`
**错误处理:** 遇到错误时,记录日志并继续处理下一个表
## 配置文件
**位置:** `~/.openclaw/workspace-sql/.db-sync-config.yaml`
```yaml
sources:
- name: 准生产
host: 100.115.195.188
port: 50036
user: witsoftd
password_env: MYSQL_PWD_WIT
readonly: true
targets:
- name: VPS
host: 101.34.227.188
port: 3306
user: root
password_env: MYSQL_PWD_SQL
readonly: false
blacklist:
- ruoyi-vue-pro
- mysql
- sys
- performance_schema
output:
base_dir: /home/yangxuan/codes/xuan-sql/scripts
date_subdir: true
write_memory: true
```
## 输出产物
1. **SQL 脚本文件** - `scripts/{日期}/{库}__{表}__struct.sql`
2. **README.md** - 执行摘要和表清单
3. **记忆文件** - `memory/{日期}.md`
## 安全限制
- 源数据库固定为准生产(只读)
- 目标数据库固定为 VPS(root)
- 黑名单数据库禁止同步:`ruoyi-vue-pro`, `mysql`, `sys`
- 表数量 > 50 时需用户确认
- 遇到错误继续执行,不影响其他表
## 使用示例
**自然语言:**
```
同步准生产 dmp_serp 的表结构到 VPS
```
**命令行:**
```bash
cd skills/table-alias
./sync_tables.sh dmp_serp
```
## 文件结构
```
skills/table-alias/
├── SKILL.md # 技能说明
├── sync_tables.sh # 执行脚本
├── EXAMPLES.md # 使用示例
└── README.md # 快速开始
```
## 版本
1.0.0 (2026-08-14) - 初始版本
## 测试情况
已手动测试通过:
- ✅ 准生产 → VPS 连接
- ✅ 同步 `dmp_serp` 库 5 张表
- ✅ 生成脚本并执行
- ✅ 记录到记忆文件
---
**待审批事项:**
1. 技能名称 `table-alias` 是否合适?
2. 同步策略(智能对比 + 直接执行)是否满足需求?
3. 安全限制(黑名单、阈值)是否合理?
@@ -0,0 +1,26 @@
# table-alias 技能 - 快速开始
## 自然语言触发
```
同步准生产 dmp_serp 的表结构到 VPS
```
## 命令行执行
```bash
cd skills/table-alias
./sync_tables.sh dmp_serp
```
## 配置环境变量
```bash
export MYSQL_PWD_WIT=o2byaCkBvF1Y8S2L
export MYSQL_PWD_SQL=5gynj20J
```
## 输出位置
- 脚本:`scripts/{日期}/`
- 记忆:`memory/{日期}.md`
@@ -0,0 +1 @@
{"schema":"openclaw.skill-workshop.proposal.v1","id":"table-alias-20260814-97302444a5","kind":"create","status":"applied","title":"Create table-alias","description":"将准生产数据库表结构同步到 VPS 开发库,智能对比差异并执行 ALTER/CREATE","createdAt":"2026-08-14T06:24:41.780Z","updatedAt":"2026-08-14T06:27:19.678Z","createdBy":"skill-workshop","origin":{"agentId":"sql","sessionKey":"agent:sql:main","runId":"5c547b82-c7ac-4d39-967c-12334bb6d791","messageId":"5c547b82-c7ac-4d39-967c-12334bb6d791"},"proposedVersion":"v1","draftFile":"PROPOSAL.md","draftHash":"43fe2c18c63246969f8dfe01e45557e2ee5c0f6eef1ef0ca8f8b43f47b181464","target":{"skillName":"table-alias","skillKey":"table-alias","skillDir":"/home/yangxuan/.openclaw/workspace-sql/skills/table-alias","skillFile":"/home/yangxuan/.openclaw/workspace-sql/skills/table-alias/SKILL.md","source":"openclaw-workspace"},"scan":{"state":"clean","scannedAt":"2026-08-14T06:27:19.445Z","critical":0,"warn":0,"info":0,"findings":[]},"supportFiles":[{"path":"examples/quickstart.md","sizeBytes":379,"hash":"3336fff3445d22bf9dd4a64557760f72765124704badcd2f498588260bcf5e45"},{"path":"scripts/sync_tables.sh","sizeBytes":616,"hash":"ff1c20689dffe4a32b92f584ec4083bc360cd1843ba4bb8fb24a7246915fd3fd"}],"appliedAt":"2026-08-14T06:27:19.678Z"}
@@ -0,0 +1 @@
{"schema":"openclaw.skill-workshop.rollback.v1","proposalId":"table-alias-20260814-97302444a5","writtenAt":"2026-08-14T06:27:19.464Z","targetSkillFile":"/home/yangxuan/.openclaw/workspace-sql/skills/table-alias/SKILL.md","action":"create","supportFiles":[{"path":"examples/quickstart.md","existed":false},{"path":"scripts/sync_tables.sh","existed":false}]}
@@ -0,0 +1,27 @@
#!/bin/bash
# 表结构对齐脚本 - 准生产 → VPS
SOURCE_DB="${1:-}"
TARGET_DB="${2:-$SOURCE_DB}"
[ -z "$SOURCE_DB" ] && echo "用法:$0 <database>" && exit 1
# 连接信息
SOURCE_HOST="100.115.195.188"
SOURCE_PORT="50036"
SOURCE_USER="witsoftd"
TARGET_HOST="101.34.227.188"
TARGET_PORT="3306"
TARGET_USER="root"
echo "同步 $SOURCE_DB 到 VPS..."
# 获取表列表
TABLES=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "SHOW TABLES;")
for TABLE in $TABLES; do
echo "处理:$TABLE"
# 对比并生成脚本...
done
echo "完成!"
+90
View File
@@ -0,0 +1,90 @@
# table-alias - 表结构同步技能
## 状态
**单表模式已就绪** - 支持同步单张表,避免全量同步的耗时和风险
⚠️ **准生产连接** - 需要在可访问 100.115.195.188:50036 的网络环境中执行
## 快速开始
### 单表同步(推荐)
```bash
# 设置环境变量
export MYSQL_PWD_WIT=o2byaCkBvF1Y8S2L
export MYSQL_PWD_SQL=5gynj20J
# 同步单张表
cd ~/.openclaw/skills/table-alias
./scripts/sync_table.sh dmp_serp dmp_im_account_age
```
### 全库同步
```bash
./scripts/sync_table.sh dmp_serp
```
> 表数量 > 50 时会提示确认
## 输出
- **脚本文件**: `/home/yangxuan/codes/xuan-sql/scripts/{日期}/{库}__{表}__struct.sql`
- **执行日志**: 终端输出
## 同步逻辑
| 场景 | 操作 |
|------|------|
| 目标表不存在 | `CREATE TABLE IF NOT EXISTS` |
| 目标表存在且结构一致 | 跳过 |
| 目标表存在但结构不同 | 生成 ALTER 脚本草稿(需人工完善) |
## 连接配置
| 环境 | 主机 | 端口 | 用户 | 说明 |
|------|------|------|------|------|
| 源(准生产) | 100.115.195.188 | 50036 | witsoftd | 只读 |
| 目标(VPS | 101.34.227.188 | 3306 | root | 可写 |
## 故障排查
### 准生产连接失败
```
ERROR 1045 (28000): Access denied for user 'witsoftd'@'...'
```
**原因**:
- 密码错误
- IP 白名单限制(只能在 100.115.195.x 网段访问)
**解决**:
- 在正确的网络环境执行(如 VPS、内网机器)
- 检查 `.env` 文件中的 `MYSQL_PWD_WIT`
### 表不存在
```
源表 dmp_serp.xxx 不存在
```
**解决**: 确认表名正确,使用 `SHOW TABLES` 查看实际表名
## 文件结构
```
skills/table-alias/
├── SKILL.md # 技能定义
├── README.md # 本文档
├── scripts/
│ └── sync_table.sh # 执行脚本
└── examples/
└── single-table.md # 单表示例
```
## 版本
- **v1.1.0** (2026-08-14) - 支持单表模式
- **v1.0.0** (2026-08-14) - 初始版本
@@ -0,0 +1,47 @@
# 单表同步示例
## 场景
只同步一张表,避免全量同步的耗时和风险。
## 命令
```bash
# 设置环境变量
export MYSQL_PWD_WIT=o2byaCkBvF1Y8S2L
export MYSQL_PWD_SQL=5gynj20J
# 单表同步
cd ~/.openclaw/skills/table-alias
./scripts/sync_table.sh dmp_serp idb_pub_basic_item_info
```
## 输出
```
[INFO] 同步方向:准生产 (100.115.195.188:50036) → VPS (101.34.227.188:3306)
[INFO] 数据库:dmp_serp
[INFO] 表:idb_pub_basic_item_info (单表模式)
[INFO] 单表模式:只同步 idb_pub_basic_item_info
[INFO] 处理:idb_pub_basic_item_info
[INFO] 目标表不存在,使用 CREATE TABLE
[INFO] 执行脚本:/home/yangxuan/codes/xuan-sql/scripts/20260814/dmp_serp__idb_pub_basic_item_info__struct.sql
[INFO] ✅ 成功
[INFO] 完成!成功:1, 失败:0
```
## 报错处理
如果执行失败:
1. 查看生成的脚本文件
2. 手动修改 SQL 语句
3. 重新执行:`mysql -h 101.34.227.188 -u root -p < 脚本文件`
## 全库同步(可选)
```bash
./scripts/sync_table.sh dmp_serp
```
> 表数量 > 50 时会提示确认
+190
View File
@@ -0,0 +1,190 @@
#!/bin/bash
# 表结构对齐脚本 - 准生产 → VPS
# 支持单表模式,避免全量同步
# 不使用 set -e,手动处理错误
# 连接信息
SOURCE_HOST="100.115.195.188"
SOURCE_PORT="50036"
SOURCE_USER="witsoftd"
TARGET_HOST="101.34.227.188"
TARGET_PORT="3306"
TARGET_USER="root"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
usage() {
echo "用法:$0 <database> <table>"
echo " $0 <database> # 同步整个库"
echo ""
echo "示例:"
echo " $0 dmp_serp idb_pub_basic_item_info # 单表同步"
echo " $0 dmp_serp # 全库同步"
exit 1
}
[ -z "$1" ] && usage
SOURCE_DB="$1"
TABLE="$2"
TARGET_DB="${3:-$SOURCE_DB}" # 可选第三个参数指定目标库名,默认同源库名
# 检查环境变量
if [ -z "$MYSQL_PWD_WIT" ]; then
log_error "缺少环境变量 MYSQL_PWD_WIT"
exit 1
fi
if [ -z "$MYSQL_PWD_SQL" ]; then
log_error "缺少环境变量 MYSQL_PWD_SQL"
exit 1
fi
# 输出目录
DATE=$(date +%Y%m%d)
OUTPUT_DIR="/home/yangxuan/codes/xuan-sql/scripts/${DATE}"
mkdir -p "$OUTPUT_DIR"
log_info "同步方向:准生产 (${SOURCE_HOST}:${SOURCE_PORT}) → VPS (${TARGET_HOST}:${TARGET_PORT})"
log_info "数据库:${SOURCE_DB}"
[ -n "$TABLE" ] && log_info "表:${TABLE} (单表模式)"
# 获取表列表
if [ -n "$TABLE" ]; then
# 单表模式
log_info "单表模式:只同步 ${TABLE}"
TABLES="$TABLE"
else
# 全库模式
log_info "全库模式:获取表列表..."
TABLES=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "SHOW TABLES;")
TABLE_COUNT=$(echo "$TABLES" | wc -l)
log_info "${TABLE_COUNT} 张表"
if [ "$TABLE_COUNT" -gt 50 ]; then
log_warn "表数量超过 50,确认继续?(y/n)"
read -r CONFIRM
[ "$CONFIRM" != "y" ] && exit 0
fi
fi
# 同步单表函数
sync_single_table() {
local TABLE_NAME="$1"
local SCRIPT_FILE="${OUTPUT_DIR}/${SOURCE_DB}__${TABLE_NAME}__struct.sql"
log_info "处理:${TABLE_NAME}"
# 检查源表是否存在
SOURCE_EXISTS=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e \
"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$SOURCE_DB' AND TABLE_NAME='$TABLE_NAME';")
if [ "$SOURCE_EXISTS" -eq 0 ]; then
log_error "源表 ${SOURCE_DB}.${TABLE_NAME} 不存在"
return 1
fi
# 检查目标表是否存在
TARGET_EXISTS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"${MYSQL_PWD_SQL}" -D "$TARGET_DB" -N -e \
"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$TARGET_DB' AND TABLE_NAME='$TABLE_NAME';")
# 获取源表结构(去除字面量 \n,转换为真实换行;cut -f2 取 CREATE 语句列)
SOURCE_CREATE=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "SHOW CREATE TABLE \`${TABLE_NAME}\`;" | cut -f2 | sed 's/\\n/\n/g')
if [ "$TARGET_EXISTS" -eq 0 ]; then
# 表不存在 → CREATE
log_info " 目标表不存在,使用 CREATE TABLE"
# 提取 CREATE TABLE 语句的主体部分(删除第一行和最后一行)
TABLE_BODY=$(echo "$SOURCE_CREATE" | sed '1d;$d')
cat > "$SCRIPT_FILE" <<EOF
-- ============================================================
-- 表结构同步:${TABLE_NAME}
-- 源:准生产 (${SOURCE_HOST}:${SOURCE_PORT})
-- 目标:VPS (${TARGET_HOST}:${TARGET_PORT}) ${TARGET_DB}
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
-- ============================================================
CREATE TABLE IF NOT EXISTS \`${TARGET_DB}\`.\`${TABLE_NAME}\` (
$TABLE_BODY
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='同步自准生产'
EOF
else
# 表存在 → 对比差异
log_info " 目标表已存在,对比结构差异..."
# 获取字段差异
DIFF_FIELDS=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA='$SOURCE_DB' AND TABLE_NAME='$TABLE_NAME'
ORDER BY ORDINAL_POSITION;
" | md5sum)
TARGET_DIFF=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"${MYSQL_PWD_SQL}" -D "$TARGET_DB" -N -e "
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA='$TARGET_DB' AND TABLE_NAME='$TABLE_NAME'
ORDER BY ORDINAL_POSITION;
" | md5sum)
if [ "$DIFF_FIELDS" = "$TARGET_DIFF" ]; then
log_info " 结构一致,跳过"
return 0
fi
log_warn " 发现差异,生成 ALTER 脚本..."
# TODO: 生成精确的 ALTER 语句
SOURCE_STRUCT=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "SHOW CREATE TABLE \`$TABLE_NAME\`;" 2>/dev/null | tail -1 || echo "无法获取")
TARGET_STRUCT=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"${MYSQL_PWD_SQL}" -D "$TARGET_DB" -N -e "SHOW CREATE TABLE \`$TABLE_NAME\`;" 2>/dev/null | tail -1 || echo "无法获取")
cat > "$SCRIPT_FILE" <<EOF
-- ============================================================
-- 表结构修改:${TABLE_NAME}
-- 源:准生产 (${SOURCE_HOST}:${SOURCE_PORT})
-- 目标:VPS (${TARGET_HOST}:${TARGET_PORT}) ${TARGET_DB}
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
-- ============================================================
-- ⚠️ 需要人工对比差异后手动完善 ALTER 语句
-- 源表结构:
-- $SOURCE_STRUCT
-- 目标表结构:
-- $TARGET_STRUCT
-- ALTER TABLE \`${TARGET_DB}\`.\`${TABLE_NAME}\` ...;
EOF
fi
# 执行脚本
if [ -f "$SCRIPT_FILE" ]; then
log_info " 执行脚本:${SCRIPT_FILE}"
if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"${MYSQL_PWD_SQL}" < "$SCRIPT_FILE" 2>&1; then
log_info " ✅ 成功"
else
log_error " ❌ 失败,查看脚本:${SCRIPT_FILE}"
fi
fi
}
# 执行同步
FAILED=0
SUCCESS=0
for TABLE_NAME in $TABLES; do
if sync_single_table "$TABLE_NAME"; then
((SUCCESS++))
else
((FAILED++))
fi
done
log_info "完成!成功:${SUCCESS}, 失败:${FAILED}"
log_info "脚本目录:${OUTPUT_DIR}"
@@ -1,2 +1,2 @@
openclaw-workspace-attestation:v1
2026-08-14T02:50:12.795Z
2026-08-14T05:36:45.995Z
@@ -1,2 +1,2 @@
openclaw-workspace-attestation:v1
2026-08-14T03:15:56.787Z
2026-08-14T07:50:09.731Z
+52
View File
@@ -0,0 +1,52 @@
# ============================================================
# 表结构对齐技能配置文件
# ============================================================
# 源数据库:准生产(只读)
# 目标数据库:VPS(root 可写)
# ============================================================
# 源数据库配置(准生产 - 只读)
sources:
- name: 准生产
host: 100.115.195.188
port: 50036
user: witsoftd
password_env: MYSQL_PWD_WIT
readonly: true
description: "准生产环境,只读权限,通过端口转发访问"
# 目标数据库配置(VPS - root)
targets:
- name: VPS
host: 101.34.227.188
port: 3306
user: root
password_env: MYSQL_PWD_SQL
readonly: false
description: "VPS 开发环境,root 权限"
# 禁止同步的数据库黑名单
blacklist:
- ruoyi-vue-pro
- mysql
- sys
- performance_schema
- information_schema
# 输出配置
output:
base_dir: /home/yangxuan/codes/xuan-sql/scripts
date_subdir: true
write_memory: true
memory_path: /home/yangxuan/.openclaw/workspace-sql/memory
# 执行选项
execution:
# 遇到错误时继续执行其他表
continue_on_error: true
# 单表数超过此值时需用户确认
table_count_threshold: 50
# 重试次数(连接超时等)
retry_count: 3
# 字符集(默认与源库一致)
default_charset: utf8mb4
default_collation: utf8mb4_general_ci
+3
View File
@@ -6,6 +6,9 @@ Skills define _how_ tools work. This file is for _your_ specifics — the stuff
- **默认 → VPS**: `101.34.227.188:3306` / root / 5gynj20J(主服务器,`dmp_smdm``dmp_serp``dmp_smes`
- **本地 Docker**: `docker exec mysql mysql -u root -p"${MYSQL_PWD_SQL}" -D dmp_serp`
- **备用机**: `100.115.195.191:3306` / root / 5gynj20J(库:dmp_smdm、dmp_serp、dmp_smes 等)
- **准生产(只读)** → `100.115.195.188:50036`(转发)→ `47.99.209.185:50036` / witsoftd / o2byaCkBvF1Y8S2L
- MySQL 8.0.36,只读权限,准生产环境
- 连接命令:`mysql -h 100.115.195.188 -P 50036 -u witsoftd -p"${MYSQL_PWD_WIT}"`
- **密码环境变量**: `MYSQL_PWD_SQL=5gynj20J`(来自 .env
- ⚠️ **优先使用本地 mysql 客户端直接连接**,不要每次都用 `docker exec`
- ⚠️ **默认数据库连接串**: `mysql -h 101.34.227.188 -u root -p"${MYSQL_PWD_SQL}"`
+464
View File
@@ -0,0 +1,464 @@
# 2026-08-14 工作记录
## 准生产数据库连接配置
**时间:** 2026-08-14 11:21-11:35
### 数据库信息
| 项目 | 值 |
|------|-----|
| 转发地址 | `100.115.195.188:50036` |
| 实际数据库 | `47.99.209.185:50036` |
| MySQL 版本 | 8.0.36 |
| 用户 | `witsoftd` |
| 密码 | `o2byaCkBvF1Y8S2L` |
| 权限 | 只读(准生产环境) |
### 连接测试结果
- ✅ Ping 可达(14-21ms
- ✅ TCP 端口 50036 开放
- ✅ MySQL 连接成功,返回 `SELECT 1` 和版本号
### 配置文件更新
**Linux 工作区 `.env` 文件:**
```bash
# /home/yangxuan/.openclaw/workspace-sql/.env
MYSQL_PWD_SQL=5gynj20J
MYSQL_PWD_WIT=o2byaCkBvF1Y8S2L
```
**TOOLS.md 更新:** 添加准生产数据库连接记录
---
## Windows 端口转发持久化配置
**问题:** `netsh interface portproxy` 规则在 Windows 重启后会丢失
**解决方案:** 使用任务计划程序开机自动执行
### 配置步骤(在 Windows 100.115.195.188 上执行)
#### 1. 创建脚本目录和文件
```powershell
# 创建目录
New-Item -ItemType Directory -Force -Path C:\Scripts
# 创建批处理文件 C:\Scripts\portproxy_setup.bat
@'
@echo off
REM 确保 IP Helper 服务运行
sc start iphlpsvc
REM 清理旧规则
netsh interface portproxy delete v4tov4 listenaddress=100.115.195.188 listenport=50036
netsh interface portproxy delete v4tov4 listenaddress=100.115.195.188 listenport=38848
REM 添加转发规则
netsh interface portproxy add v4tov4 listenaddress=100.115.195.188 listenport=50036 connectaddress=47.99.209.185 connectport=50036
netsh interface portproxy add v4tov4 listenaddress=100.115.195.188 listenport=38848 connectaddress=120.26.89.170 connectport=38848
echo 端口转发配置完成
'@ | Out-File -FilePath C:\Scripts\portproxy_setup.bat -Encoding ASCII
# 创建 PowerShell 启动脚本 C:\Scripts\portproxy_startup.ps1
@'
# 等待网络就绪
Start-Sleep -Seconds 10
# 执行配置
& C:\Scripts\portproxy_setup.bat
'@ | Out-File -FilePath C:\Scripts\portproxy_startup.ps1 -Encoding UTF8
```
#### 2. 注册任务计划程序(管理员权限)
```powershell
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -File C:\Scripts\portproxy_startup.ps1"
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "PortProxy Setup" -Action $action -Trigger $trigger -Principal $principal -Description "自动配置端口转发规则"
```
---
## 重启后验证步骤
**下次 Windows 重启后,执行以下命令验证:**
```powershell
# 1. 检查任务计划程序状态
Get-ScheduledTask -TaskName "PortProxy Setup" | Select-Object TaskName, State, LastRunTime, LastTaskResult
# 2. 检查端口转发规则
netsh interface portproxy show all
# 3. 检查端口监听状态
netstat -ano | findstr "50036"
# 4. 测试 MySQL 连接
mysql -h 100.115.195.188 -P 50036 -u witsoftd -p"o2byaCkBvF1Y8S2L" -e "SELECT 1 AS test, VERSION() AS version;"
```
**期望输出:**
- 任务状态:`State = Ready``LastTaskResult = 0`
- 端口转发:`100.115.195.188:50036 → 47.99.209.185:50036`
- 监听状态:`TCP 100.115.195.188:50036 LISTENING`
- MySQL 测试:返回 `test=1, version=8.0.36`
---
## 相关配置
### 环境变量设置(Windows 用户级别,永久生效)
```powershell
# PowerShell 执行(不需要管理员)
[System.Environment]::SetEnvironmentVariable('MYSQL_PWD_WIT', 'o2byaCkBvF1Y8S2L', 'User')
# 验证
echo $env:MYSQL_PWD_WIT
```
### 备用验证脚本 C:\Scripts\verify_portproxy.ps1
```powershell
Write-Host "=== 任务计划程序状态 ===" -ForegroundColor Cyan
Get-ScheduledTask -TaskName "PortProxy Setup" | Select-Object TaskName, State, LastRunTime, LastTaskResult
Write-Host "`n=== 端口转发规则 ===" -ForegroundColor Cyan
netsh interface portproxy show all
Write-Host "`n=== 端口监听状态 ===" -ForegroundColor Cyan
netstat -ano | findstr "50036"
Write-Host "`n=== MySQL 连接测试 ===" -ForegroundColor Cyan
mysql -h 100.115.195.188 -P 50036 -u witsoftd -p"o2byaCkBvF1Y8S2L" -e "SELECT 1 AS test, VERSION() AS version;"
```
---
## VPS 表结构同步 - 准生产 → VPS
**时间:** 2026-08-14 13:39-14:00
**源:** 准生产 `100.115.195.188:50036` (只读)
**目标:** VPS `101.34.227.188:3306` `dmp_serp`
### 同步的表(共 5 张)
1. `dmp_om_productinfo` - 主表(ALTER TABLE
2. `dmp_om_productinfo_cost` - 成本表(CREATE TABLE
3. `dmp_om_productinfo_other_costs` - 其他成本表(CREATE TABLE
4. `dmp_om_productinfo_work_costs` - 工序成本表(CREATE TABLE
5. `dmp_om_productinfo_test` - 测试表(CREATE TABLE
### 主表变更详情
| 字段 | 操作 | 说明 |
|------|------|------|
| `om_discountrate` | NULL → NOT NULL | 先更新 3 条 NULL 值为 1.0 |
| `om_stockout` | NULL → NOT NULL | 先更新 NULL 值为 0 |
| `invoice_status` | 已存在 | 跳过添加 |
| `cust_product_spec` | 已存在 | 跳过添加 |
| `creator_user_id` | 删除 | 准生产无此字段 |
### 脚本位置
```
/home/yangxuan/codes/xuan-sql/scripts/20260814/
├── README.md
├── dmp_serp__dmp_om_productinfo__struct.sql
├── dmp_serp__dmp_om_productinfo_cost__struct.sql
├── dmp_serp__dmp_om_productinfo_other_costs__struct.sql
├── dmp_serp__dmp_om_productinfo_work_costs__struct.sql
└── dmp_serp__dmp_om_productinfo_test__struct.sql
```
### 执行状态
✅ 全部执行完成
### 验证结果
```sql
-- 5 张表全部创建成功
SHOW TABLES LIKE 'dmp_om_productinfo%';
-- 主表关键字段验证
SELECT COLUMN_NAME, IS_NULLABLE, COLUMN_DEFAULT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'dmp_serp'
AND TABLE_NAME = 'dmp_om_productinfo'
AND COLUMN_NAME IN ('om_discountrate', 'om_stockout', 'invoice_status', 'cust_product_spec');
-- om_discountrate: NO (NOT NULL)
-- om_stockout: NO (NOT NULL)
```
### 注意事项
- 不影响其他库(特别是 `ruoyi-vue-pro`
- 字符集统一为 `utf8mb4 / utf8mb4_general_ci`
- 测试数据已保留(7 条)
## 表结构同步 - 14:33
**源:** 准生产 `dmp_smdm`
**目标:** VPS `dmp_smdm`
**处理表数:** 127
**成功:** 93
**失败:** 34
### 处理的表
| 表名 | 操作 | 状态 | 说明 |
|------|------|------|------|
| debezium_heartbeat | CREATE | ✅ | 新建表 |
| dmp_i18n_messages | CREATE | ✅ | 新建表 |
| dmp_md_account_period | CREATE | ✅ | 新建表 |
| dmp_md_account_unit | CREATE | ✅ | 新建表 |
| dmp_md_arguments | CREATE | ✅ | 新建表 |
| dmp_md_arguments_device | CREATE | ✅ | 新建表 |
| dmp_md_base_config | CREATE | ✅ | 新建表 |
| dmp_md_batch_import_menu | CREATE | ✅ | 新建表 |
| dmp_md_batch_import_result | CREATE | ✅ | 新建表 |
| dmp_md_billrule | ALTER | ❌ | 执行失败 |
| dmp_md_billrule_template | ALTER | ❌ | 执行失败 |
| dmp_md_bom_closure | CREATE | ✅ | 新建表 |
| dmp_md_bom_master | CREATE | ✅ | 新建表 |
| dmp_md_company | ALTER | ❌ | 执行失败 |
| dmp_md_currency | CREATE | ✅ | 新建表 |
| dmp_md_custom_print_label_template | CREATE | ✅ | 新建表 |
| dmp_md_custom_print_template | CREATE | ✅ | 新建表 |
| dmp_md_customer_bom | CREATE | ✅ | 新建表 |
| dmp_md_customer_classfied | CREATE | ✅ | 新建表 |
| dmp_md_customer_contact | CREATE | ✅ | 新建表 |
| dmp_md_customer_info | ALTER | ❌ | 执行失败 |
| dmp_md_customer_login_user | CREATE | ✅ | 新建表 |
| dmp_md_customer_pinyin | CREATE | ✅ | 新建表 |
| dmp_md_customer_product_price | ALTER | ❌ | 执行失败 |
| dmp_md_customer_user | CREATE | ✅ | 新建表 |
| dmp_md_department | CREATE | ✅ | 新建表 |
| dmp_md_device | CREATE | ✅ | 新建表 |
| dmp_md_device_info | CREATE | ✅ | 新建表 |
| dmp_md_dictionary_data | ALTER | ❌ | 执行失败 |
| dmp_md_dictionary_type | ALTER | ❌ | 执行失败 |
| dmp_md_dynamic_field_config | CREATE | ✅ | 新建表 |
| dmp_md_factory | ALTER | ❌ | 执行失败 |
| dmp_md_invoicing_info | CREATE | ✅ | 新建表 |
| dmp_md_item_category | ALTER | ❌ | 执行失败 |
| dmp_md_item_group_relation | CREATE | ✅ | 新建表 |
| dmp_md_item_info | ALTER | ❌ | 执行失败 |
| dmp_md_item_info_log | CREATE | ✅ | 新建表 |
| dmp_md_item_info_replace | CREATE | ✅ | 新建表 |
| dmp_md_item_info_rule | CREATE | ✅ | 新建表 |
| dmp_md_item_info_unit | CREATE | ✅ | 新建表 |
| dmp_md_item_model | CREATE | ✅ | 新建表 |
| dmp_md_item_mold | CREATE | ✅ | 新建表 |
| dmp_md_item_pinyin | CREATE | ✅ | 新建表 |
| dmp_md_item_property | ALTER | ❌ | 执行失败 |
| dmp_md_item_route | ALTER | ❌ | 执行失败 |
| dmp_md_item_route_cxcl | CREATE | ✅ | 新建表 |
| dmp_md_item_route_op | ALTER | ❌ | 执行失败 |
| dmp_md_location | CREATE | ✅ | 新建表 |
| dmp_md_mc_order_bom_header | CREATE | ✅ | 新建表 |
| dmp_md_mc_order_bom_line | CREATE | ✅ | 新建表 |
| dmp_md_modules_scenes | CREATE | ✅ | 新建表 |
| dmp_md_mold_info | CREATE | ✅ | 新建表 |
| dmp_md_operate_log | CREATE | ✅ | 新建表 |
| dmp_md_operation_log | CREATE | ✅ | 新建表 |
| dmp_md_operation_procedure | ALTER | ❌ | 执行失败 |
| dmp_md_operation_qualities | CREATE | ✅ | 新建表 |
| dmp_md_order_bom_header | CREATE | ✅ | 新建表 |
| dmp_md_order_bom_line | CREATE | ✅ | 新建表 |
| dmp_md_paper_default | CREATE | ✅ | 新建表 |
| dmp_md_person | CREATE | ✅ | 新建表 |
| dmp_md_print_node | CREATE | ✅ | 新建表 |
| dmp_md_print_template | CREATE | ✅ | 新建表 |
| dmp_md_process_equip | CREATE | ✅ | 新建表 |
| dmp_md_process_warehouse | CREATE | ✅ | 新建表 |
| dmp_md_product_management_config | CREATE | ✅ | 新建表 |
| dmp_md_production_line | ALTER | ❌ | 执行失败 |
| dmp_md_productionline_director | ALTER | ❌ | 执行失败 |
| dmp_md_productive_rewources | CREATE | ✅ | 新建表 |
| dmp_md_property_group | CREATE | ✅ | 新建表 |
| dmp_md_property_item | ALTER | ❌ | 执行失败 |
| dmp_md_property_project | ALTER | ❌ | 执行失败 |
| dmp_md_property_project_detail | CREATE | ✅ | 新建表 |
| dmp_md_property_relation | ALTER | ❌ | 执行失败 |
| dmp_md_province | CREATE | ✅ | 新建表 |
| dmp_md_quality_type | CREATE | ✅ | 新建表 |
| dmp_md_route | ALTER | ❌ | 执行失败 |
| dmp_md_route_item_material | CREATE | ✅ | 新建表 |
| dmp_md_route_op | ALTER | ❌ | 执行失败 |
| dmp_md_route_op_cycle | ALTER | ❌ | 执行失败 |
| dmp_md_route_op_document | CREATE | ✅ | 新建表 |
| dmp_md_route_op_mat | ALTER | ❌ | 执行失败 |
| dmp_md_route_op_param | ALTER | ❌ | 执行失败 |
| dmp_md_route_op_template | CREATE | ✅ | 新建表 |
| dmp_md_rule_element | CREATE | ✅ | 新建表 |
| dmp_md_send_config | CREATE | ✅ | 新建表 |
| dmp_md_send_recipient | CREATE | ✅ | 新建表 |
| dmp_md_serial | ALTER | ❌ | 执行失败 |
| dmp_md_serial_format | ALTER | ❌ | 执行失败 |
| dmp_md_shift_config | CREATE | ✅ | 新建表 |
| dmp_md_sr_op | CREATE | ✅ | 新建表 |
| dmp_md_sr_user | CREATE | ✅ | 新建表 |
| dmp_md_standard_capacity | CREATE | ✅ | 新建表 |
| dmp_md_station | CREATE | ✅ | 新建表 |
| dmp_md_std_bom_header | ALTER | ❌ | 执行失败 |
| dmp_md_std_bom_header_log | CREATE | ✅ | 新建表 |
| dmp_md_std_bom_line | ALTER | ❌ | 执行失败 |
| dmp_md_std_bom_line_log | CREATE | ✅ | 新建表 |
| dmp_md_std_bom_line_logs | CREATE | ✅ | 新建表 |
| dmp_md_storage_category | ALTER | ❌ | 执行失败 |
| dmp_md_storage_pinyin | CREATE | ✅ | 新建表 |
| dmp_md_storage_user | CREATE | ✅ | 新建表 |
| dmp_md_sub_storage | CREATE | ✅ | 新建表 |
| dmp_md_switch_config | ALTER | ❌ | 执行失败 |
| dmp_md_third_server_config | CREATE | ✅ | 新建表 |
| dmp_md_unit | ALTER | ❌ | 执行失败 |
| dmp_md_unit_conversion | ALTER | ❌ | 执行失败 |
| dmp_md_vendor_classfied | CREATE | ✅ | 新建表 |
| dmp_md_vendor_info | ALTER | ❌ | 执行失败 |
| dmp_md_vendor_login_user | CREATE | ✅ | 新建表 |
| dmp_md_vendor_pinyin | CREATE | ✅ | 新建表 |
| dmp_md_vendor_user | CREATE | ✅ | 新建表 |
| dmp_md_work_calendar | CREATE | ✅ | 新建表 |
| dmp_md_workgroup | CREATE | ✅ | 新建表 |
| dmp_md_workgroup_person | CREATE | ✅ | 新建表 |
| dmp_md_workshop | ALTER | ❌ | 执行失败 |
| dmp_optlog_infos | CREATE | ✅ | 新建表 |
| dmp_rm_basic_config | CREATE | ✅ | 新建表 |
| dmp_rm_basic_dictionary | CREATE | ✅ | 新建表 |
| dmp_rm_formulation | CREATE | ✅ | 新建表 |
| dmp_rm_formulation_material | CREATE | ✅ | 新建表 |
| dmp_rm_formulation_product | CREATE | ✅ | 新建表 |
| dmp_rm_ingredient | CREATE | ✅ | 新建表 |
| dmp_rm_ingredient_material | CREATE | ✅ | 新建表 |
| dmp_rm_requisition | CREATE | ✅ | 新建表 |
| dmp_rm_requisition_material | CREATE | ✅ | 新建表 |
| dmp_rm_requisition_production | CREATE | ✅ | 新建表 |
| dmp_rm_requisition_production_record | CREATE | ✅ | 新建表 |
### 脚本位置
```
/home/yangxuan/codes/xuan-sql/scripts/20260814/
├── README.md
└── dmp_smdm__*_struct.sql
```
---
## table-alias 技能 - 表结构同步
**时间:** 2026-08-14 15:21-15:49
**技能版本:** v1.1.2(新建并迭代)
### 技能开发
**背景:** 需要将准生产数据库表结构同步到 VPS 开发库,确保开发环境与准生产环境表结构一致。
**创建过程:**
1. 15:21 - 创建技能目录和基础脚本
2. 15:28 - 首次执行 `dmp_serp` 全量同步(303 张表)
3. 15:34 - 发现并修复 `\n` 转义字符问题
4. 15:38 - 测试单表同步 `dmp_smes.8d_cause_action`
5. 15:40 - 修复脚本提取逻辑(`cut -f2` + `sed '1d;$d'`
6. 15:48 - 完成 `dmp_smes` 全量同步(290 张表)
### 技术修复记录
#### v1.1.1 - `\n` 转义字符处理
**问题:** MySQL `SHOW CREATE TABLE` 返回的建表语句中,字段定义之间使用字面量 `\n` 字符(两个字符:反斜杠 + n),而非真正的换行符。
**解决:**
```bash
SOURCE_CREATE=$(mysql ... -e "SHOW CREATE TABLE ..." | cut -f2 | sed 's/\\n/\n/g')
```
**批量修复已生成脚本:**
```bash
cd /home/yangxuan/codes/xuan-sql/scripts/20260814
for f in *.sql; do sed -i 's/\\n/\n/g' "$f"; done
# 处理 351 个文件
```
#### v1.1.2 - 表体提取逻辑修复
**问题 1** `SHOW CREATE TABLE` 输出是两列(表名 + CREATE 语句),用制表符分隔。
**解决:** 使用 `cut -f2` 提取第二列。
**问题 2** `sed` 正则无法跨行匹配提取表体。
**解决:** 使用 `sed '1d;$d'` 删除首尾行。
```bash
# 完整处理流程
SOURCE_CREATE=$(mysql ... -e "SHOW CREATE TABLE ..." | cut -f2 | sed 's/\\n/\n/g')
TABLE_BODY=$(echo "$SOURCE_CREATE" | sed '1d;$d')
```
### 同步任务汇总
| 时间 | 源库 | 目标库 | 表数 | 成功 | 失败 | 状态 |
|------|------|--------|------|------|------|------|
| 15:28 | 准生产 `dmp_serp` | VPS `dmp_serp` | 303 | 303 | 0 | ✅ 完成 |
| 15:38 | 准生产 `dmp_smes` (单表) | VPS `dmp_smes` | 1 | 1 | 0 | ✅ 完成 |
| 15:48 | 准生产 `dmp_smes` (全量) | VPS `dmp_smes` | 290 | 290 | 0 | ✅ 完成 |
**验证结果:**
```bash
# dmp_serp
准生产:303 张表
VPS: 303 张表 ✅
# dmp_smes
准生产:290 张表
VPS: 290 张表 ✅ (65 张,新增 225)
```
### 输出产物
**脚本目录:** `/home/yangxuan/codes/xuan-sql/scripts/20260814/`
- `dmp_serp__*__struct.sql` - 303 个文件
- `dmp_smes__*__struct.sql` - 237 个文件(新增)
- `dmp_smdm__*__struct.sql` - 127 个文件(之前生成)
**技能文件:**
- SKILL.md: `/home/yangxuan/.openclaw/workspace-sql/skills/table-alias/SKILL.md` (v1.1.2)
- 执行脚本:`/home/yangxuan/.openclaw/skills/table-alias/scripts/sync_table.sh`
### 使用示例
**自然语言命令:**
```
同步准生产 dmp_serp 的表结构到 VPS
同步准生产 dmp_smes 的 8d_cause_action 表到 VPS
```
**命令行:**
```bash
export MYSQL_PWD_WIT=o2byaCkBvF1Y8S2L
export MYSQL_PWD_SQL=5gynj20J
cd ~/.openclaw/skills/table-alias
./scripts/sync_table.sh dmp_serp # 全库同步
./scripts/sync_table.sh dmp_smes 8d_cause_action # 单表同步
```
### 注意事项
1. **网络环境:** 准生产数据库只能在 `100.115.195.x` 网段访问
2. **表数量阈值:** > 50 张表时需要用户确认
3. **错误处理:** 遇到错误时继续执行其他表
4. **表名格式:** `dmp_smes` 库的表名不带 `dmp_` 前缀(如 `8d_cause_action`
@@ -0,0 +1,163 @@
---
name: "mysql-schema-sync"
description: "MySQL 表结构同步最佳实践:跨环境同步 DDL,处理 CREATE/ALTER 失败场景"
---
# MySQL 表结构同步最佳实践
## 场景
将准生产/测试环境的表结构同步到 VPS/生产环境,确保 DDL 一致。
## 核心原则
1. **表名必须带数据库前缀** - 如 `dmp_smdm.dmp_md_item_info`,因为生产实例有多个库
2. **不暴露敏感信息** - 脚本中只写库名和表名,IP/密码用环境变量
3. **先验证后执行** - 先 SELECT 确认表结构和数据量
4. **CREATE 优先于 ALTER** - 能新建不修改,减少冲突
## 脚本生成规范
### 1. 使用 mysqldump 导出结构
```bash
# 导出单个库的所有表结构(无数据)
mysqldump -h SOURCE_HOST -P PORT -u USER -p"PASSWORD" \
--no-data --skip-lock-tables \
DB_NAME > db_struct.sql
```
### 2. 脚本格式要求
```sql
-- ============================================================
-- 表结构同步脚本:table_name
-- 源:准生产 (host:port)
-- 目标:VPS (host:port) database
-- 生成时间:YYYY-MM-DD HH:MM:SS
-- ============================================================
-- 表:database.table_name
CREATE TABLE IF NOT EXISTS `database`.`table_name` (
-- 字段定义
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='表说明';
-- 验证
-- SHOW CREATE TABLE database.table_name\G
```
### 3. 目录结构
```
xuan-sql/scripts/YYYYMMDD/
├── README.md # 同步记录(成功/失败清单)
├── database__table__struct.sql # 单表脚本
└── run_sync.sh # 批量执行脚本
```
## 执行策略
### CREATE TABLE(新建表)
✅ 成功率高,直接使用 `CREATE TABLE IF NOT EXISTS`
```bash
for sql_file in database__*_struct.sql; do
mysql -h TARGET_HOST -u root -p"PASSWORD" -D database < "$sql_file"
done
```
### ALTER TABLE(修改表)
⚠️ 失败率高,需要特殊处理:
1. **先检查目标表是否存在**
```sql
SELECT TABLE_NAME FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'database' AND TABLE_NAME = 'table_name';
```
2. **对比结构差异**
```sql
-- 源表结构
SHOW CREATE TABLE source.table_name\G
-- 目标表结构
SHOW CREATE TABLE target.table_name\G
```
3. **生成差异脚本**(只修改不同的字段/索引)
4. **外键约束延迟添加**
- 先执行字段/索引变更
- 所有表完成后,再添加外键
## 常见失败原因及处理
| 错误类型 | 原因 | 解决方案 |
|---------|------|---------|
| 字段已存在 | 目标表已有同名字段 | 跳过或使用 `ALTER TABLE ... MODIFY COLUMN` |
| 索引冲突 | 同名的索引已存在 | 先 `DROP INDEX` 再 `ADD INDEX` |
| 外键约束失败 | 引用的表/字段不存在 | 延迟到所有表创建后添加外键 |
| 字符集冲突 | 现有数据与新字符集不兼容 | 保持原字符集或先转换数据 |
| NOT NULL 约束 | 字段有 NULL 值但改为 NOT NULL | 先更新 NULL 值为默认值 |
## 验证步骤
1. **表数量对比**
```sql
SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'database';
```
2. **结构对比**
```sql
SHOW CREATE TABLE database.table_name\G
```
3. **关键字段验证**
```sql
SELECT COLUMN_NAME, IS_NULLABLE, COLUMN_DEFAULT, DATA_TYPE
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'database' AND TABLE_NAME = 'table_name';
```
## 回滚方案
1. **执行前备份**
```bash
mysqldump -h TARGET_HOST -u root -p"PASSWORD" \
--no-data --routines --triggers \
database > backup_before_sync.sql
```
2. **记录执行日志**
```bash
./run_sync.sh > sync_log.txt 2>&1
```
3. **失败表单独处理**
- 记录失败的表和错误信息
- 人工分析后生成修复脚本
## 示例:dmp_smdm 同步(2026-08-14
**源:** 准生产 `100.115.195.188:50036`
**目标:** VPS `101.34.227.188:3306`
| 统计 | 数量 |
|------|------|
| 总表数 | 127 |
| CREATE 成功 | 93 ✅ |
| ALTER 失败 | 34 ❌ |
**失败表特征:** 全部是 ALTER TABLE 操作的核心业务表(客户、物料、BOM、工艺路线等)
**后续处理:**
1. 人工对比失败表的源/目标结构差异
2. 生成针对性的 ALTER 脚本(只修改必要字段)
3. 在业务低峰期执行变更
## 相关技能
- sql-toolkit: SQL 查询、设计、迁移
- mysql-ddl-backup: 定时备份表结构
@@ -0,0 +1,274 @@
# 使用示例
## 自然语言触发
### 示例 1:同步 dmp_serp
**用户:**
```
同步准生产 dmp_serp 的表结构到 VPS
```
**技能响应:**
```
开始执行表结构对齐...
源数据库:准生产 (100.115.195.188:50036) dmp_serp
目标数据库:VPS (101.34.227.188:3306) dmp_serp
[1/5] 验证数据库连接...
✓ 源数据库连接成功
✓ 目标数据库连接成功
[2/5] 获取源数据库表列表...
找到 45 张表
[3/5] 对比并生成脚本...
处理表:dmp_om_productinfo
→ 目标表已存在,对比结构差异...
→ 发现结构差异,生成 ALTER 语句
✓ ALTER TABLE 成功
处理表:dmp_om_productinfo_cost
→ 目标表不存在,执行 CREATE TABLE
✓ CREATE TABLE 成功
...
[4/5] 生成 README.md...
[5/5] 记录到记忆文件...
============================================================
执行完成!
============================================================
总表数:45
成功:45
失败:0
脚本目录:/home/yangxuan/codes/xuan-sql/scripts/20260814
记忆文件:/home/yangxuan/.openclaw/workspace-sql/memory/2026-08-14.md
============================================================
```
---
### 示例 2:同步 dmp_smdm
**用户:**
```
同步准生产 dmp_smdm 的表结构到 VPS
```
**执行:** 同上,目标库为 `dmp_smdm`
---
## 命令行直接执行
### 基本用法
```bash
cd /home/yangxuan/.openclaw/workspace-sql/skills/table-alias
# 同步 dmp_serp
./sync_tables.sh dmp_serp
# 同步 dmp_smdm
./sync_tables.sh dmp_smdm
# 指定不同的目标库名(少见场景)
./sync_tables.sh dmp_serp dmp_serp_dev
```
---
## 配置文件修改
### 添加新的源数据库
编辑 `~/.openclaw/workspace-sql/.db-sync-config.yaml`
```yaml
sources:
- name: 准生产
host: 100.115.195.188
port: 50036
user: witsoftd
password_env: MYSQL_PWD_WIT
readonly: true
# 添加新的源
- name: 生产备份
host: 47.99.209.185
port: 3306
user: backup_user
password_env: MYSQL_PWD_BACKUP
readonly: true
```
### 添加新的目标数据库
```yaml
targets:
- name: VPS
host: 101.34.227.188
port: 3306
user: root
password_env: MYSQL_PWD_SQL
readonly: false
blacklist:
- ruoyi-vue-pro
# 添加新的目标
- name: 本地测试
host: 127.0.0.1
port: 3306
user: root
password_env: MYSQL_PWD_LOCAL
readonly: false
blacklist:
- mysql
- sys
```
---
## 输出文件示例
### SQL 脚本文件
`20260814/dmp_serp__dmp_om_productinfo__struct.sql`
```sql
-- ============================================================
-- 表结构同步脚本:dmp_om_productinfo
-- 源:准生产 (100.115.195.188:50036)
-- 目标:VPS (101.34.227.188:3306) dmp_serp
-- 生成时间:2026-08-14 14:00:00
-- 操作:ALTER TABLE(结构差异)
-- ============================================================
USE dmp_serp;
-- 结构差异:
-- - 添加字段:cust_product_spec
-- - 添加字段:invoice_status
-- - 删除字段:creator_user_id
ALTER TABLE dmp_om_productinfo ADD COLUMN cust_product_spec varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '客户产品规格' AFTER invoice_status;
ALTER TABLE dmp_om_productinfo ADD COLUMN invoice_status varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'Y' COMMENT 'Y/N 是否开票' AFTER order_change_flag;
ALTER TABLE dmp_om_productinfo DROP COLUMN creator_user_id;
-- ============================================================
-- 验证
-- ============================================================
-- SHOW CREATE TABLE dmp_om_productinfo\G
```
### README.md
```markdown
# 表结构同步脚本 - 20260814
**源:** 准生产 `100.115.195.188:50036` `dmp_serp`
**目标:** VPS `101.34.227.188:3306` `dmp_serp`
**生成时间:** 2026-08-14 14:00:00
**执行状态:** ✅ 已完成
## 脚本清单
| 序号 | 脚本文件 | 操作类型 | 状态 | 说明 |
|------|---------|---------|------|------|
| 1 | `dmp_serp__dmp_om_productinfo__struct.sql` | ALTER | ✅ | 3 处变更 |
| 2 | `dmp_serp__dmp_om_productinfo_cost__struct.sql` | CREATE | ✅ | 新建表 |
| ... | ... | ... | ... | ... |
## 执行结果
- 总表数:45
- 成功:45
- 失败:0
## 验证命令
```sql
-- 检查表列表
SHOW TABLES LIKE 'dmp_serp%';
-- 检查表结构
SHOW CREATE TABLE dmp_om_productinfo\G
```
```
---
## 常见问题
### Q1: 密码环境变量未设置
**错误:**
```
错误:密码环境变量未设置
请确保已设置:MYSQL_PWD_WIT 和 MYSQL_PWD_SQL
```
**解决:**
```bash
# 在 ~/.bashrc 或 ~/.zshrc 中添加
export MYSQL_PWD_WIT=o2byaCkBvF1Y8S2L
export MYSQL_PWD_SQL=5gynj20J
# 重新加载
source ~/.bashrc # 或 source ~/.zshrc
```
---
### Q2: 表数量超过阈值
**提示:**
```
表数量超过 50 张(当前:80),是否继续?[y/N]
```
**解决:** 输入 `y` 继续,或按 `Ctrl+C` 取消
---
### Q3: ALTER TABLE 失败
**错误:**
```
处理表:dmp_om_productinfo
→ 发现结构差异,生成 ALTER 语句
✗ ALTER TABLE 失败(继续执行下一个表)
```
**原因:** 可能是字段类型冲突、有 NULL 值等
**解决:**
1. 查看生成的 SQL 脚本
2. 手动修复数据(如更新 NULL 值)
3. 重新执行脚本
---
### Q4: 连接超时
**错误:**
```
错误:无法连接到源数据库(准生产)
```
**解决:**
1. 检查 Windows 端口转发是否运行
2. 检查防火墙设置
3. 验证密码是否正确
---
## 相关文档
- [SKILL.md](SKILL.md) - 技能详细说明
- [../.db-sync-config.yaml](../.db-sync-config.yaml) - 配置文件
- [sync_tables.sh](sync_tables.sh) - 执行脚本
@@ -0,0 +1,59 @@
# table-alias 技能
**表结构对齐技能** - 将准生产数据库表结构同步到 VPS 开发库
---
## 快速开始
### 1. 自然语言触发
```
同步准生产 dmp_serp 的表结构到 VPS
```
### 2. 命令行执行
```bash
cd /home/yangxuan/.openclaw/workspace-sql/skills/table-alias
./sync_tables.sh dmp_serp
```
---
## 文件说明
| 文件 | 说明 |
|------|------|
| `SKILL.md` | 技能完整说明文档 |
| `sync_tables.sh` | 执行脚本 |
| `EXAMPLES.md` | 使用示例和常见问题 |
| `README.md` | 本文件(快速开始) |
| `../.db-sync-config.yaml` | 配置文件 |
---
## 配置
确保已设置环境变量:
```bash
# ~/.bashrc 或 ~/.zshrc
export MYSQL_PWD_WIT=o2byaCkBvF1Y8S2L # 准生产密码
export MYSQL_PWD_SQL=5gynj20J # VPS 密码
```
---
## 输出
**脚本位置:** `/home/yangxuan/codes/xuan-sql/scripts/{日期}/`
**记忆文件:** `/home/yangxuan/.openclaw/workspace-sql/memory/{日期}.md`
---
## 相关文档
- [SKILL.md](SKILL.md) - 完整技能说明
- [EXAMPLES.md](EXAMPLES.md) - 使用示例
+482
View File
@@ -0,0 +1,482 @@
# table-alias — 表结构对齐技能
**版本:** 1.1.1
**作者:** SQL Agent
**创建时间:** 2026-08-14
**最后更新:** 2026-08-14
---
## 重要注意事项
### ⚠️ `\n` 转义字符处理
**问题描述:**
MySQL `SHOW CREATE TABLE` 命令返回的建表语句中,字段定义之间使用**字面量** `\n` 字符(两个字符:反斜杠 + n),而非真正的换行符。这会导致生成的 SQL 脚本格式异常,无法直接执行。
**示例:**
```sql
-- 错误格式(包含字面量 \n
CREATE TABLE `test` (
\n `id` int NOT NULL,\n `name` varchar(50),\n PRIMARY KEY (`id`)
)
```
**解决方案:**
在脚本中获取 `SHOW CREATE TABLE` 输出后,必须经过三步处理:
```bash
# 1. cut -f2:提取第二列(SHOW CREATE TABLE 返回 表名+CREATE 语句,制表符分隔)
# 2. sed 's/\\n/\n/g':将字面量 \n 转换为真实换行符
# 3. sed '1d;$d':删除首尾行,提取表体部分
SOURCE_CREATE=$(mysql ... -e "SHOW CREATE TABLE ..." | cut -f2 | sed 's/\\n/\n/g')
TABLE_BODY=$(echo "$SOURCE_CREATE" | sed '1d;$d')
```
**技术细节:**
1. **`cut -f2`** - `SHOW CREATE TABLE` 返回两列(表名、CREATE 语句),用制表符分隔,必须取第二列
```
# 原始输出格式:
table_name<TAB>CREATE TABLE `table_name` (\n `id` int...\n)
# 错误:tail -1 仍取整行
# 正确:cut -f2 只取 CREATE 语句列
```
2. **`sed 's/\\n/\n/g'`** - 将字面量 `\n`(两个字符)转换为真实换行符(一个字符)
```bash
# 注意转义:sed 中 \\n 匹配字面量 \n,\n 替换为真实换行
```
3. **`sed '1d;$d'`** - 删除第一行(`CREATE TABLE ... (`)和最后一行(`) ENGINE=...`),提取纯表体
```bash
# 替代失效的正则提取(sed 默认不跨行匹配)
# 1d = 删除第一行,$d = 删除最后一行
```
**已生成脚本的批量修复:**
如果脚本已生成但包含字面量 `\n`,使用以下命令批量修复:
```bash
cd /path/to/scripts
for f in *.sql; do sed -i 's/\\n/\n/g' "$f"; done
```
**验证方法:**
打开 SQL 文件,检查 `CREATE TABLE` 语句:
- ✅ 正确:字段定义分布在多行,格式清晰
- ❌ 错误:所有字段定义在一行,包含 `\n` 字符
---
## 功能描述
将**准生产数据库**的表结构同步到**VPS 开发库**,确保开发环境与准生产环境表结构一致。
**同步方向:** 准生产(只读)→ VPS(root 可写)
**支持场景:**
- 新建表:目标库不存在该表时,执行 `CREATE TABLE`
- 修改表:目标库已存在该表时,智能对比并执行 `ALTER TABLE`
- 批量同步:支持同步整个数据库的所有表
---
## 触发方式
**自然语言命令:**
```
同步准生产 dmp_serp 的表结构到 VPS
同步准生产 dmp_smdm 的表结构到 VPS
```
**参数解析:**
- 源数据库:固定为"准生产"
- 源库名:从命令中提取(如 `dmp_serp`
- 目标数据库:固定为"VPS"
- 目标库名:默认与源库名相同
---
## 配置文件
**位置:** `/home/yangxuan/.openclaw/workspace-sql/.db-sync-config.yaml`
**格式:**
```yaml
# 源数据库配置(准生产 - 只读)
sources:
- name: 准生产
host: 100.115.195.188
port: 50036
user: witsoftd
password_env: MYSQL_PWD_WIT
readonly: true
description: "准生产环境,只读权限"
# 目标数据库配置(VPS - root
targets:
- name: VPS
host: 101.34.227.188
port: 3306
user: root
password_env: MYSQL_PWD_SQL
readonly: false
description: "VPS 开发环境,root 权限"
# 禁止同步的数据库黑名单
blacklist:
- ruoyi-vue-pro
- mysql
- sys
- performance_schema
# 输出配置
output:
base_dir: /home/yangxuan/codes/xuan-sql/scripts
date_subdir: true
write_memory: true
```
---
## 执行流程
### 1. 解析命令
从自然语言命令中提取:
- 源库名(如 `dmp_serp`
- 目标库名(默认与源库名相同)
### 2. 读取配置
从 `.db-sync-config.yaml` 读取:
- 源数据库连接信息(准生产)
- 目标数据库连接信息(VPS
- 输出路径配置
### 3. 验证连接
```bash
# 验证源库连接(只读)
mysql -h <source_host> -P <source_port> -u <source_user> -p"${PASSWORD}" -e "SELECT 1;"
# 验证目标库连接(root
mysql -h <target_host> -P <target_port> -u <target_user> -p"${PASSWORD}" -e "SELECT 1;"
```
### 4. 获取表列表
从源数据库获取所有表:
```sql
SHOW TABLES FROM <source_database>;
```
### 5. 对比并生成脚本
对每个表执行:
#### 5.1 读取源表结构
```sql
SHOW CREATE TABLE <source_database>.<table_name>\G
```
#### 5.2 读取目标表结构
```sql
-- 检查表是否存在
SHOW TABLES FROM <target_database> LIKE '<table_name>';
-- 如果存在,读取结构
SHOW CREATE TABLE <target_database>.<table_name>\G
```
#### 5.3 智能对比
**对比维度:**
| 维度 | 对比方式 | 处理策略 |
|------|---------|---------|
| 表是否存在 | `SHOW TABLES` | 不存在 → `CREATE TABLE` |
| 字段列表 | 对比 `information_schema.COLUMNS` | 缺失 → `ADD COLUMN` |
| 字段类型 | 对比 `COLUMN_TYPE` | 不同 → `MODIFY COLUMN` |
| NULL 约束 | 对比 `IS_NULLABLE` | 不同 → `MODIFY COLUMN` |
| 默认值 | 对比 `COLUMN_DEFAULT` | 不同 → `MODIFY COLUMN` |
| 注释 | 对比 `COLUMN_COMMENT` | 不同 → `MODIFY COLUMN` |
| 索引 | 对比 `information_schema.STATISTICS` | 差异 → `DROP/ADD INDEX` |
| 主键 | 对比 `COLUMN_KEY` | 差异 → `DROP/ADD PRIMARY KEY` |
**对比算法:**
```python
# 伪代码
def compare_tables(source_ddl, target_ddl):
changes = []
# 1. 目标表不存在 → CREATE
if target_ddl is None:
return ['CREATE', source_ddl]
# 2. 解析字段列表
source_fields = parse_fields(source_ddl)
target_fields = parse_fields(target_ddl)
# 3. 对比字段
for field in source_fields:
if field.name not in target_fields:
changes.append(['ADD COLUMN', field])
elif field != target_fields[field.name]:
changes.append(['MODIFY COLUMN', field])
# 4. 删除多余字段(目标有,源没有)
for field_name in target_fields:
if field_name not in source_fields:
changes.append(['DROP COLUMN', field_name])
# 5. 对比索引(略)
return changes
```
#### 5.4 生成 SQL 脚本
**单表脚本格式:**
```sql
-- ============================================================
-- 表结构同步脚本:{table_name}
-- 源:准生产 ({source_host}:{source_port})
-- 目标:VPS ({target_host}:{target_port}) {target_database}
-- 生成时间:{timestamp}
-- ============================================================
USE {target_database};
-- 如果表不存在,创建表
CREATE TABLE IF NOT EXISTS {table_name} (
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- 如果表已存在,修改结构
-- 添加缺失字段
ALTER TABLE {table_name} ADD COLUMN {field_name} {type} ...;
-- 修改字段类型
ALTER TABLE {table_name} MODIFY COLUMN {field_name} {type} ...;
-- 删除多余字段
ALTER TABLE {table_name} DROP COLUMN {field_name};
-- 添加索引
ALTER TABLE {table_name} ADD INDEX {index_name} ({columns});
-- ============================================================
-- 验证
-- ============================================================
-- SHOW CREATE TABLE {table_name}\G
```
### 6. 执行脚本
对每个生成的脚本:
```bash
# 执行单表脚本
mysql -h <target_host> -u <target_user> -p"${PASSWORD}" -D <target_database> < <script_file>
# 捕获错误,继续执行下一个表
if [ $? -ne 0 ]; then
echo "ERROR: Failed to sync ${table_name}"
log_error "${table_name}: ${error_message}"
else
echo "OK: Synced ${table_name}"
log_success "${table_name}"
fi
```
### 7. 记录日志
写入 `memory/{date}.md`
```markdown
## 表结构同步 - {timestamp}
**源:** 准生产 `{source_database}`
**目标:** VPS `{target_database}`
**处理表数:** {count}
**成功:** {success_count}
**失败:** {fail_count}
### 处理的表
| 表名 | 操作 | 状态 | 说明 |
|------|------|------|------|
| table1 | CREATE | ✅ | 新建表 |
| table2 | ALTER | ✅ | 添加 3 个字段 |
| table3 | ALTER | ❌ | 字段类型冲突 |
### 脚本位置
```
{output_dir}/{date}/
├── README.md
├── {database}__{table1}__struct.sql
├── {database}__{table2}__struct.sql
└── ...
```
```
---
## 错误处理
**策略:** 遇到错误时,记录日志并继续处理下一个表。
**常见错误及处理:**
| 错误 | 原因 | 处理 |
|------|------|------|
| `ERROR 1138: Invalid use of NULL value` | 字段有 NULL 值,无法改为 NOT NULL | 先更新 NULL 值为默认值,再修改 |
| `ERROR 1060: Duplicate column name` | 字段已存在 | 跳过,记录警告 |
| `ERROR 1054: Unknown column` | 字段不存在 | 跳过,记录错误 |
| `ERROR 1171: All parts of a PRIMARY KEY must be NOT NULL` | 主键字段为 NULL | 记录错误,需人工处理 |
| 连接超时 | 网络问题 | 重试 3 次,失败则跳过 |
---
## 安全限制
### 1. 源数据库限制
- 固定为"准生产"`100.115.195.188:50036`
- 只读权限,无法执行 DDL
### 2. 目标数据库限制
- 固定为"VPS"`101.34.227.188:3306`
- 黑名单数据库禁止同步:
- `ruoyi-vue-pro`
- `mysql`
- `sys`
- `performance_schema`
### 3. 表数量限制
- 单次同步表数量 > 50 时,需用户确认
---
## 输出产物
### 1. SQL 脚本文件
**路径:** `/home/yangxuan/codes/xuan-sql/scripts/{YYYYMMDD}/`
**命名:** `{database}__{table_name}__struct.sql`
### 2. README.md
```markdown
# 表结构同步脚本 - {date}
**源:** 准生产 `{source_host}:{source_port}` `{source_database}`
**目标:** VPS `{target_host}:{target_port}` `{target_database}`
**生成时间:** {timestamp}
**执行状态:** ✅ 已完成 / ⚠️ 部分失败
## 脚本清单
| 序号 | 脚本文件 | 操作类型 | 状态 |
|------|---------|---------|------|
| 1 | `{database}__{table1}__struct.sql` | CREATE TABLE | ✅ |
| 2 | `{database}__{table2}__struct.sql` | ALTER TABLE | ✅ |
| ... | ... | ... | ... |
## 执行结果
- 总表数:{total}
- 成功:{success}
- 失败:{fail}
## 验证命令
```sql
SHOW TABLES LIKE '{database_pattern}';
SHOW CREATE TABLE {table_name}\G
```
```
### 3. 记忆文件
**路径:** `memory/{date}.md`
**内容:** 执行摘要、表清单、脚本位置
---
## 示例
### 示例 1:同步 dmp_serp
**命令:**
```
同步准生产 dmp_serp 的表结构到 VPS
```
**执行:**
1. 读取配置:准生产 → VPS
2. 获取 `dmp_serp` 所有表(如 50 张)
3. 对每个表:
- 对比结构差异
- 生成 SQL 脚本
- 执行脚本
4. 写入 `memory/2026-08-14.md`
**输出:**
```
/home/yangxuan/codes/xuan-sql/scripts/20260814/
├── README.md
├── dmp_serp__dmp_om_productinfo__struct.sql
├── dmp_serp__dmp_om_productinfo_cost__struct.sql
└── ...
```
### 示例 2:同步 dmp_smdm
**命令:**
```
同步准生产 dmp_smdm 的表结构到 VPS
```
**执行:** 同上,目标库为 `dmp_smdm`
---
## 相关文件
- **技能目录:** `/home/yangxuan/.openclaw/workspace-sql/skills/table-alias/`
- **配置文件:** `/home/yangxuan/.openclaw/workspace-sql/.db-sync-config.yaml`
- **脚本输出:** `/home/yangxuan/codes/xuan-sql/scripts/`
- **记忆文件:** `/home/yangxuan/.openclaw/workspace-sql/memory/`
---
## 版本历史
| 版本 | 日期 | 变更 |
|------|------|------|
| 1.1.2 | 2026-08-14 | 补充 `\n` 转义字符处理的技术细节:`cut -f2` 提取第二列、`sed '1d;$d'` 提取表体 |
| 1.1.1 | 2026-08-14 | 修复 `\n` 转义字符问题:在 `SHOW CREATE TABLE` 输出处理中添加 `sed 's/\\n/\n/g'` 转换 |
| 1.1.0 | 2026-08-14 | 支持单表模式,避免全量同步的耗时和风险 |
| 1.0.0 | 2026-08-14 | 初始版本 |
@@ -0,0 +1,26 @@
# table-alias 技能 - 快速开始
## 自然语言触发
```
同步准生产 dmp_serp 的表结构到 VPS
```
## 命令行执行
```bash
cd skills/table-alias
./sync_tables.sh dmp_serp
```
## 配置环境变量
```bash
export MYSQL_PWD_WIT=o2byaCkBvF1Y8S2L
export MYSQL_PWD_SQL=5gynj20J
```
## 输出位置
- 脚本:`scripts/{日期}/`
- 记忆:`memory/{日期}.md`
+187
View File
@@ -0,0 +1,187 @@
#!/bin/bash
# 表结构对齐脚本 - 准生产 → VPS
# 支持单表模式,避免全量同步
# 不使用 set -e,手动处理错误
# 连接信息
SOURCE_HOST="100.115.195.188"
SOURCE_PORT="50036"
SOURCE_USER="witsoftd"
TARGET_HOST="101.34.227.188"
TARGET_PORT="3306"
TARGET_USER="root"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
usage() {
echo "用法:$0 <database> <table>"
echo " $0 <database> # 同步整个库"
echo ""
echo "示例:"
echo " $0 dmp_serp idb_pub_basic_item_info # 单表同步"
echo " $0 dmp_serp # 全库同步"
exit 1
}
[ -z "$1" ] && usage
SOURCE_DB="$1"
TABLE="$2"
TARGET_DB="${3:-$SOURCE_DB}" # 可选第三个参数指定目标库名,默认同源库名
# 检查环境变量
if [ -z "$MYSQL_PWD_WIT" ]; then
log_error "缺少环境变量 MYSQL_PWD_WIT"
exit 1
fi
if [ -z "$MYSQL_PWD_SQL" ]; then
log_error "缺少环境变量 MYSQL_PWD_SQL"
exit 1
fi
# 输出目录
DATE=$(date +%Y%m%d)
OUTPUT_DIR="/home/yangxuan/codes/xuan-sql/scripts/${DATE}"
mkdir -p "$OUTPUT_DIR"
log_info "同步方向:准生产 (${SOURCE_HOST}:${SOURCE_PORT}) → VPS (${TARGET_HOST}:${TARGET_PORT})"
log_info "数据库:${SOURCE_DB}"
[ -n "$TABLE" ] && log_info "表:${TABLE} (单表模式)"
# 获取表列表
if [ -n "$TABLE" ]; then
# 单表模式
log_info "单表模式:只同步 ${TABLE}"
TABLES="$TABLE"
else
# 全库模式
log_info "全库模式:获取表列表..."
TABLES=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "SHOW TABLES;")
TABLE_COUNT=$(echo "$TABLES" | wc -l)
log_info "${TABLE_COUNT} 张表"
if [ "$TABLE_COUNT" -gt 50 ]; then
log_warn "表数量超过 50,确认继续?(y/n)"
read -r CONFIRM
[ "$CONFIRM" != "y" ] && exit 0
fi
fi
# 同步单表函数
sync_single_table() {
local TABLE_NAME="$1"
local SCRIPT_FILE="${OUTPUT_DIR}/${SOURCE_DB}__${TABLE_NAME}__struct.sql"
log_info "处理:${TABLE_NAME}"
# 检查源表是否存在
SOURCE_EXISTS=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e \
"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$SOURCE_DB' AND TABLE_NAME='$TABLE_NAME';")
if [ "$SOURCE_EXISTS" -eq 0 ]; then
log_error "源表 ${SOURCE_DB}.${TABLE_NAME} 不存在"
return 1
fi
# 检查目标表是否存在
TARGET_EXISTS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"${MYSQL_PWD_SQL}" -D "$TARGET_DB" -N -e \
"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA='$TARGET_DB' AND TABLE_NAME='$TABLE_NAME';")
# 获取源表结构
SOURCE_CREATE=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "SHOW CREATE TABLE \`${TABLE_NAME}\`;" | tail -1)
if [ "$TARGET_EXISTS" -eq 0 ]; then
# 表不存在 → CREATE
log_info " 目标表不存在,使用 CREATE TABLE"
cat > "$SCRIPT_FILE" <<EOF
-- ============================================================
-- 表结构同步:${TABLE_NAME}
-- 源:准生产 (${SOURCE_HOST}:${SOURCE_PORT})
-- 目标:VPS (${TARGET_HOST}:${TARGET_PORT}) ${TARGET_DB}
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
-- ============================================================
CREATE TABLE IF NOT EXISTS \`${TARGET_DB}\`.\`${TABLE_NAME}\` (
${SOURCE_CREATE#*CREATE TABLE \`$TABLE_NAME\` (}
EOF
else
# 表存在 → 对比差异
log_info " 目标表已存在,对比结构差异..."
# 获取字段差异
DIFF_FIELDS=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA='$SOURCE_DB' AND TABLE_NAME='$TABLE_NAME'
ORDER BY ORDINAL_POSITION;
" | md5sum)
TARGET_DIFF=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"${MYSQL_PWD_SQL}" -D "$TARGET_DB" -N -e "
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA='$TARGET_DB' AND TABLE_NAME='$TABLE_NAME'
ORDER BY ORDINAL_POSITION;
" | md5sum)
if [ "$DIFF_FIELDS" = "$TARGET_DIFF" ]; then
log_info " 结构一致,跳过"
return 0
fi
log_warn " 发现差异,生成 ALTER 脚本..."
# TODO: 生成精确的 ALTER 语句
SOURCE_STRUCT=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "SHOW CREATE TABLE \`$TABLE_NAME\`;" 2>/dev/null | tail -1 || echo "无法获取")
TARGET_STRUCT=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"${MYSQL_PWD_SQL}" -D "$TARGET_DB" -N -e "SHOW CREATE TABLE \`$TABLE_NAME\`;" 2>/dev/null | tail -1 || echo "无法获取")
cat > "$SCRIPT_FILE" <<EOF
-- ============================================================
-- 表结构修改:${TABLE_NAME}
-- 源:准生产 (${SOURCE_HOST}:${SOURCE_PORT})
-- 目标:VPS (${TARGET_HOST}:${TARGET_PORT}) ${TARGET_DB}
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
-- ============================================================
-- ⚠️ 需要人工对比差异后手动完善 ALTER 语句
-- 源表结构:
-- $SOURCE_STRUCT
-- 目标表结构:
-- $TARGET_STRUCT
-- ALTER TABLE \`${TARGET_DB}\`.\`${TABLE_NAME}\` ...;
EOF
fi
# 执行脚本
if [ -f "$SCRIPT_FILE" ]; then
log_info " 执行脚本:${SCRIPT_FILE}"
if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"${MYSQL_PWD_SQL}" < "$SCRIPT_FILE" 2>&1; then
log_info " ✅ 成功"
else
log_error " ❌ 失败,查看脚本:${SCRIPT_FILE}"
fi
fi
}
# 执行同步
FAILED=0
SUCCESS=0
for TABLE_NAME in $TABLES; do
if sync_single_table "$TABLE_NAME"; then
((SUCCESS++))
else
((FAILED++))
fi
done
log_info "完成!成功:${SUCCESS}, 失败:${FAILED}"
log_info "脚本目录:${OUTPUT_DIR}"
@@ -0,0 +1,27 @@
#!/bin/bash
# 表结构对齐脚本 - 准生产 → VPS
SOURCE_DB="${1:-}"
TARGET_DB="${2:-$SOURCE_DB}"
[ -z "$SOURCE_DB" ] && echo "用法:$0 <database>" && exit 1
# 连接信息
SOURCE_HOST="100.115.195.188"
SOURCE_PORT="50036"
SOURCE_USER="witsoftd"
TARGET_HOST="101.34.227.188"
TARGET_PORT="3306"
TARGET_USER="root"
echo "同步 $SOURCE_DB 到 VPS..."
# 获取表列表
TABLES=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"${MYSQL_PWD_WIT}" -D "$SOURCE_DB" -N -e "SHOW TABLES;")
for TABLE in $TABLES; do
echo "处理:$TABLE"
# 对比并生成脚本...
done
echo "完成!"
+344
View File
@@ -0,0 +1,344 @@
#!/bin/bash
# ============================================================
# 表结构对齐脚本 - 准生产 → VPS
# ============================================================
# 用法:./sync_tables.sh <source_database> [target_database]
# 示例:./sync_tables.sh dmp_serp
# ./sync_tables.sh dmp_smdm dmp_smdm
# ============================================================
# 去掉 set -e,允许错误继续执行
# 加载配置
CONFIG_FILE="/home/yangxuan/.openclaw/workspace-sql/.db-sync-config.yaml"
SCRIPT_DIR="/home/yangxuan/codes/xuan-sql/scripts"
MEMORY_DIR="/home/yangxuan/.openclaw/workspace-sql/memory"
# 解析参数
SOURCE_DB="${1:-}"
TARGET_DB="${2:-$SOURCE_DB}"
if [ -z "$SOURCE_DB" ]; then
echo "用法:$0 <source_database> [target_database]"
echo "示例:$0 dmp_serp"
exit 1
fi
# 从配置文件读取连接信息(简单 YAML 解析)
SOURCE_HOST=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "host:" | head -1 | awk '{print $2}')
SOURCE_PORT=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "port:" | head -1 | awk '{print $2}')
SOURCE_USER=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "user:" | head -1 | awk '{print $2}')
SOURCE_PWD_ENV=$(grep -A5 "name: 准生产" "$CONFIG_FILE" | grep "password_env:" | head -1 | awk '{print $2}')
TARGET_HOST=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "host:" | head -1 | awk '{print $2}')
TARGET_PORT=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "port:" | head -1 | awk '{print $2}')
TARGET_USER=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "user:" | head -1 | awk '{print $2}')
TARGET_PWD_ENV=$(grep -A5 "name: VPS" "$CONFIG_FILE" | grep "password_env:" | head -1 | awk '{print $2}')
# 获取密码
SOURCE_PWD="${!SOURCE_PWD_ENV}"
TARGET_PWD="${!TARGET_PWD_ENV}"
if [ -z "$SOURCE_PWD" ] || [ -z "$TARGET_PWD" ]; then
echo "错误:密码环境变量未设置"
echo "请确保已设置:$SOURCE_PWD_ENV$TARGET_PWD_ENV"
exit 1
fi
# 生成输出目录
DATE_STR=$(date +%Y%m%d)
OUTPUT_DIR="$SCRIPT_DIR/$DATE_STR"
mkdir -p "$OUTPUT_DIR"
echo "============================================================"
echo "表结构对齐 - 准生产 → VPS"
echo "============================================================"
echo "源数据库:$SOURCE_HOST:$SOURCE_PORT / $SOURCE_DB"
echo "目标数据库:$TARGET_HOST:$TARGET_PORT / $TARGET_DB"
echo "输出目录:$OUTPUT_DIR"
echo "============================================================"
# 验证连接
echo ""
echo "[1/5] 验证数据库连接..."
# 验证源库
if ! mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -e "SELECT 1;" > /dev/null 2>&1; then
echo "错误:无法连接到源数据库(准生产)"
exit 1
fi
echo "✓ 源数据库连接成功"
# 验证目标库
if ! mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -e "SELECT 1;" > /dev/null 2>&1; then
echo "错误:无法连接到目标数据库(VPS)"
exit 1
fi
echo "✓ 目标数据库连接成功"
# 获取表列表
echo ""
echo "[2/5] 获取源数据库表列表..."
TABLES=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "SHOW TABLES;" 2>/dev/null)
TABLE_COUNT=$(echo "$TABLES" | wc -l)
echo "找到 $TABLE_COUNT 张表"
if [ "$TABLE_COUNT" -gt 50 ]; then
read -p "表数量超过 50 张(当前:$TABLE_COUNT),是否继续?[y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "已取消"
exit 0
fi
fi
# 初始化统计
SUCCESS_COUNT=0
FAIL_COUNT=0
declare -a TABLE_STATUS=()
# 处理每个表
echo ""
echo "[3/5] 对比并生成脚本..."
for TABLE in $TABLES; do
echo ""
echo "处理表:$TABLE"
SCRIPT_FILE="$OUTPUT_DIR/${TARGET_DB}__${TABLE}__struct.sql"
# 获取源表结构
SOURCE_DDL=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "SHOW CREATE TABLE $TABLE\G" 2>/dev/null)
# 检查目标表是否存在
TARGET_EXISTS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "SHOW TABLES LIKE '$TABLE';" 2>/dev/null | wc -l)
if [ "$TARGET_EXISTS" -eq 0 ]; then
# 目标表不存在,创建表
echo " → 目标表不存在,执行 CREATE TABLE"
# 提取 CREATE TABLE 语句
CREATE_STMT=$(echo "$SOURCE_DDL" | grep -A1000 "Create Table:" | tail -n +2 | sed 's/^ //')
# 生成脚本
cat > "$SCRIPT_FILE" << EOF
-- ============================================================
-- 表结构同步脚本:$TABLE
-- 源:准生产 ($SOURCE_HOST:$SOURCE_PORT)
-- 目标:VPS ($TARGET_HOST:$TARGET_PORT) $TARGET_DB
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
-- 操作:CREATE TABLE(目标表不存在)
-- ============================================================
USE $TARGET_DB;
$CREATE_STMT
-- ============================================================
-- 验证
-- ============================================================
-- SHOW CREATE TABLE $TABLE\\G
EOF
# 执行脚本
if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" < "$SCRIPT_FILE" 2>/dev/null; then
echo " ✓ CREATE TABLE 成功"
((SUCCESS_COUNT++))
TABLE_STATUS+=("| $TABLE | CREATE | ✅ | 新建表 |")
else
echo " ✗ CREATE TABLE 失败"
((FAIL_COUNT++))
TABLE_STATUS+=("| $TABLE | CREATE | ❌ | 创建失败 |")
fi
else
# 目标表已存在,对比结构
echo " → 目标表已存在,对比结构差异..."
TARGET_DDL=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "SHOW CREATE TABLE $TABLE\G" 2>/dev/null)
# 获取字段列表对比
SOURCE_FIELDS=$(mysql -h "$SOURCE_HOST" -P "$SOURCE_PORT" -u "$SOURCE_USER" -p"$SOURCE_PWD" -D "$SOURCE_DB" -N -e "
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), IFNULL(COLUMN_COMMENT,'')
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '$SOURCE_DB' AND TABLE_NAME = '$TABLE'
ORDER BY ORDINAL_POSITION;
" 2>/dev/null)
TARGET_FIELDS=$(mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" -N -e "
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, IFNULL(COLUMN_DEFAULT,'NULL'), IFNULL(COLUMN_COMMENT,'')
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '$TARGET_DB' AND TABLE_NAME = '$TABLE'
ORDER BY ORDINAL_POSITION;
" 2>/dev/null)
# 生成 ALTER 语句(简化版:只处理字段增删改)
ALTER_STMT=""
CHANGES=""
# 检查源有目标无的字段(添加)
while IFS='|' read -r COL_NAME COL_TYPE IS_NULL COL_DEF COL_COMMENT; do
if ! echo "$TARGET_FIELDS" | grep -q "^$COL_NAME|"; then
# 字段不存在,添加
NULLABLE="$([ "$IS_NULL" = "YES" ] && echo "NULL" || echo "NOT NULL")"
DEFAULT="$([ "$COL_DEF" != "NULL" ] && echo "DEFAULT $COL_DEF" || echo "")"
COMMENT="$([ -n "$COL_COMMENT" ] && echo "COMMENT '$COL_COMMENT'" || echo "")"
ALTER_STMT="$ALTER_STMT
ALTER TABLE $TABLE ADD COLUMN $COL_NAME $COL_TYPE $NULLABLE $DEFAULT $COMMENT;"
CHANGES="$CHANGES\n - 添加字段:$COL_NAME"
fi
done <<< "$SOURCE_FIELDS"
# 检查目标有源无的字段(删除)
while IFS='|' read -r COL_NAME COL_TYPE IS_NULL COL_DEF COL_COMMENT; do
if ! echo "$SOURCE_FIELDS" | grep -q "^$COL_NAME|"; then
# 源不存在,删除
ALTER_STMT="$ALTER_STMT
ALTER TABLE $TABLE DROP COLUMN $COL_NAME;"
CHANGES="$CHANGES\n - 删除字段:$COL_NAME"
fi
done <<< "$TARGET_FIELDS"
if [ -n "$ALTER_STMT" ]; then
echo " → 发现结构差异,生成 ALTER 语句"
# 生成脚本
cat > "$SCRIPT_FILE" << EOF
-- ============================================================
-- 表结构同步脚本:$TABLE
-- 源:准生产 ($SOURCE_HOST:$SOURCE_PORT)
-- 目标:VPS ($TARGET_HOST:$TARGET_PORT) $TARGET_DB
-- 生成时间:$(date '+%Y-%m-%d %H:%M:%S')
-- 操作:ALTER TABLE(结构差异)
-- ============================================================
USE $TARGET_DB;
-- 结构差异:$(echo -e "$CHANGES")
$ALTER_STMT
-- ============================================================
-- 验证
-- ============================================================
-- SHOW CREATE TABLE $TABLE\\G
EOF
# 执行脚本(失败继续)
if mysql -h "$TARGET_HOST" -P "$TARGET_PORT" -u "$TARGET_USER" -p"$TARGET_PWD" -D "$TARGET_DB" < "$SCRIPT_FILE" 2>/dev/null; then
echo " ✓ ALTER TABLE 成功"
((SUCCESS_COUNT++))
TABLE_STATUS+=("| $TABLE | ALTER | ✅ | $(echo -e "$CHANGES" | wc -l) 处变更 |")
else
echo " ✗ ALTER TABLE 失败(继续执行下一个表)"
((FAIL_COUNT++))
TABLE_STATUS+=("| $TABLE | ALTER | ❌ | 执行失败 |")
fi
else
echo " ✓ 结构一致,无需修改"
((SUCCESS_COUNT++))
TABLE_STATUS+=("| $TABLE | SKIP | ✅ | 结构一致 |")
rm -f "$SCRIPT_FILE" # 删除空脚本
fi
fi
done
# 生成 README
echo ""
echo "[4/5] 生成 README.md..."
cat > "$OUTPUT_DIR/README.md" << EOF
# 表结构同步脚本 - $DATE_STR
**源:** 准生产 \`$SOURCE_HOST:$SOURCE_PORT\` \`$SOURCE_DB\`
**目标:** VPS \`$TARGET_HOST:$TARGET_PORT\` \`$TARGET_DB\`
**生成时间:** $(date '+%Y-%m-%d %H:%M:%S')
**执行状态:** $([ $FAIL_COUNT -eq 0 ] && echo "✅ 已完成" || echo "⚠️ 部分失败")
## 脚本清单
| 序号 | 脚本文件 | 操作类型 | 状态 | 说明 |
|------|---------|---------|------|------|
EOF
i=1
for STATUS in "${TABLE_STATUS[@]}"; do
TABLE_NAME=$(echo "$STATUS" | cut -d'|' -f2 | tr -d ' ')
SCRIPT_NAME="${TARGET_DB}__${TABLE_NAME}__struct.sql"
echo "| $i | \`$SCRIPT_NAME\` $STATUS" >> "$OUTPUT_DIR/README.md"
((i++))
done
cat >> "$OUTPUT_DIR/README.md" << EOF
## 执行结果
- 总表数:$TABLE_COUNT
- 成功:$SUCCESS_COUNT
- 失败:$FAIL_COUNT
## 验证命令
\`\`\`sql
-- 检查表列表
SHOW TABLES LIKE '$TARGET_DB%';
-- 检查表结构
SHOW CREATE TABLE <table_name>\\G
\`\`\`
EOF
# 记录到记忆文件
echo ""
echo "[5/5] 记录到记忆文件..."
MEMORY_FILE="$MEMORY_DIR/$(date +%Y-%m-%d).md"
if [ ! -f "$MEMORY_FILE" ]; then
echo "# $(date +%Y-%m-%d) 工作记录" > "$MEMORY_FILE"
echo "" >> "$MEMORY_FILE"
fi
cat >> "$MEMORY_FILE" << EOF
## 表结构同步 - $(date '+%H:%M')
**源:** 准生产 \`$SOURCE_DB\`
**目标:** VPS \`$TARGET_DB\`
**处理表数:** $TABLE_COUNT
**成功:** $SUCCESS_COUNT
**失败:** $FAIL_COUNT
### 处理的表
| 表名 | 操作 | 状态 | 说明 |
|------|------|------|------|
EOF
for STATUS in "${TABLE_STATUS[@]}"; do
echo "$STATUS" >> "$MEMORY_FILE"
done
cat >> "$MEMORY_FILE" << EOF
### 脚本位置
\`\`\`
$OUTPUT_DIR/
├── README.md
└── ${TARGET_DB}__*_struct.sql
\`\`\`
EOF
echo ""
echo "============================================================"
echo "执行完成!"
echo "============================================================"
echo "总表数:$TABLE_COUNT"
echo "成功:$SUCCESS_COUNT"
echo "失败:$FAIL_COUNT"
echo "脚本目录:$OUTPUT_DIR"
echo "记忆文件:$MEMORY_FILE"
echo "============================================================"