# Liquibase Changelog 使用说明

> 适用模块：`fs-service`（所有依赖它的启动模块启动时自动执行）  
> 配置入口：`fs-service/src/main/resources/application-common.yml` → `fs.liquibase.*`

---

## 1. 目录结构

```
db/changelog/
├── db.changelog-master.xml          ← 主入口（唯一入口，必须用 XML）
├── baseline/
│   ├── init-meta.sql                ← 创建 MD5 元数据表
│   └── baseline.sql                 ← 基线标记（不改表结构）
├── changes/
│   └── 20260613-live-user-add-is-del.sql   ← 业务 DDL 变更
└── draft/                           ← 草稿目录，不会被 master 引用，启动不执行
    └── 20260615-live-user-drop-is-del.sql
```

| 目录/文件 | 作用 |
|-----------|------|
| `db.changelog-master.xml` | 按顺序 `include` 子 changelog，Liquibase 只认这一份入口 |
| `baseline/` | 首次接入、元数据表、基线 tag，一般很少改 |
| `changes/` | 日常 DDL/DML 变更，**新增 SQL 放这里** |
| `draft/` | 未纳入 master 的草稿，不参与 MD5 门禁，不执行 |

---

## 2. `db.changelog-master.xml` 用法

### 2.1 文件作用

这是 **Liquibase 唯一主入口**。应用启动时，`LiquibaseStartupGate` 读取配置：

```yaml
fs.liquibase.changelog: db/changelog/db.changelog-master.xml
```

然后通过 XML 的 `<include>` 依次加载 SQL 子文件并执行其中的 changeset。

> **注意**：SQL 格式 **不支持** `--include`。主入口必须是 XML（或 YAML），不能写成 `db.changelog-master.sql`。

### 2.2 当前内容

```xml
<include file="baseline/init-meta.sql" relativeToChangelogFile="true"/>
<include file="baseline/baseline.sql" relativeToChangelogFile="true"/>
<include file="changes/20260613-live-user-add-is-del.sql" relativeToChangelogFile="true"/>
```

| 属性 | 说明 |
|------|------|
| `file` | 相对 master 文件的路径 |
| `relativeToChangelogFile="true"` | 路径相对于 `db.changelog-master.xml` 所在目录解析 |

**执行顺序 = include 的书写顺序**，上面的文件会按 1 → 2 → 3 依次执行。

### 2.3 新增一条 SQL 变更的标准步骤

1. 在 `changes/` 下新建文件，建议命名：`YYYYMMDD-模块-简述.sql`  
   例：`20260620-order-add-index.sql`
2. 在文件内写 changeset（见第 3 节格式）
3. 在 `db.changelog-master.xml` **末尾**追加一行 include：

```xml
<include file="changes/20260620-order-add-index.sql" relativeToChangelogFile="true"/>
```

4. 本地启动应用（或 `mvn compile` 后完整重启）
5. 查库确认：

```sql
SELECT ID, AUTHOR, EXECTYPE, DATEEXECUTED, FILENAME
FROM DATABASECHANGELOG
ORDER BY DATEEXECUTED DESC;
```

### 2.4 不要做的事

| 错误做法 | 后果 |
|----------|------|
| 只改 SQL 文件名，不更新 master 里的 `file=` | Liquibase 找不到文件或仍读旧路径 |
| 把 SQL 放在 `changes/` 但不写进 master | 文件存在但不会执行 |
| 用 SQL 文件当 master 并写 `--include` | 解析 0 条 changeset，门禁报错 |
| 修改 **已发布且已执行** 的 changeset 内容 | checksum 不一致，Liquibase 校验失败 |
| 在 `draft/` 里放正式变更却不 include | 永远不会执行 |

### 2.5 启动门禁简要流程

```
启动 → 读 master.xml → 解析 changeset
     → 对比 DATABASECHANGELOG（有无未执行/缺失记录）
     → 有 pending 则 liquibase.update()
     → 全部写入 DATABASECHANGELOG 后同步 bundle MD5（database_init_meta 表）
     → 失败则禁止启动（fail-fast: true）
```

MD5 参与计算的文件：`db/changelog/` 下 `sql/xml/yaml/yml`，**排除 `draft/` 目录**。

---

## 3. `20260613-live-user-add-is-del.sql` 字段说明

该文件包含 **2 个 changeset**（一个文件可写多条，按顺序执行）。

### 3.1 文件头（必须）

```sql
--liquibase formatted sql
```

声明这是 Liquibase formatted SQL。缺少此行，整个文件不会被识别。

---

### 3.2 Changeset 1：为 `live_user` 增加 `is_del`

```sql
--changeset yhq:20260613-live-user-add-is-del
```

| 部分 | 值 | 说明 |
|------|-----|------|
| **author** | `yhq` | 变更作者/工号，写入 `DATABASECHANGELOG.AUTHOR` |
| **id** | `20260613-live-user-add-is-del` | 全局唯一标识，写入 `DATABASECHANGELOG.ID` |

> 格式必须是 `author:id`，**中间有英文冒号**。  
> 错误示例：`--changeset 20260613-live-user-add-is-del`（缺 author，会导致 0 条解析）

**前置条件：**

```sql
--preconditions onFail:MARK_RAN
--precondition-sql-check expectedResult:1 SELECT ... table_name = 'live_user'
--precondition-sql-check expectedResult:0 SELECT ... column_name = 'is_del'
```

| 行 | 含义 |
|----|------|
| `onFail:MARK_RAN` | 前置不满足时不报错，标记为已执行（`EXECTYPE=MARK_RAN`），跳过 SQL |
| `expectedResult:1` + 表存在查询 | 要求 `live_user` 表必须存在 |
| `expectedResult:0` + 列不存在查询 | 要求 `is_del` 列尚不存在才执行 ADD |

> formatted SQL 的 precondition 请用 `--precondition-sql-check`，不要用 `--precondition-column-exists` 等非 SQL 格式写法。

**DDL 与字段：**

```sql
ALTER TABLE live_user
    ADD COLUMN is_del TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除 0-未删除 1-已删除';
```

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `is_del` | `TINYINT NOT NULL` | `0` | 逻辑删除：`0` 未删除，`1` 已删除 |

**回滚（可选）：**

```sql
--rollback ALTER TABLE live_user DROP COLUMN is_del;
```

---

### 3.3 Changeset 2：为 `fs_course_play_source_config` 增加 `integral_goods`

```sql
--changeset yhq:20260613-fs-course-play-source-config-add-integral-goods
```

| 部分 | 值 | 说明 |
|------|-----|------|
| **author** | `yhq` | 同上 |
| **id** | `20260613-fs-course-play-source-config-add-integral-goods` | 与 changeset 1 同文件但 **id 必须不同** |

**前置条件：**

| 检查 | 说明 |
|------|------|
| 表 `fs_course_play_source_config` 存在 | `expectedResult:1` |
| 列 `integral_goods` 不存在 | `expectedResult:0` |

**DDL 与字段：**

```sql
ALTER TABLE fs_course_play_source_config
    ADD COLUMN integral_goods VARCHAR(2000) NULL DEFAULT NULL COMMENT '积分商品配置';
```

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `integral_goods` | `VARCHAR(2000) NULL` | `NULL` | 积分商品配置，一般为 JSON/文本配置 |

业务侧（如 `WxMaConfiguration`）启动时会查询该列，**必须先通过 Liquibase 执行本 changeset**。

**回滚：**

```sql
--rollback ALTER TABLE fs_course_play_source_config DROP COLUMN integral_goods;
```

---

## 4. 新增 changeset 模板（复制使用）

```sql
--liquibase formatted sql

--changeset yhq:YYYYMMDD-简述
--preconditions onFail:MARK_RAN
--precondition-sql-check expectedResult:1 SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = '你的表名'
--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '你的表名' AND column_name = '你的列名'
ALTER TABLE 你的表名 ADD COLUMN 你的列名 ...;
--rollback ALTER TABLE 你的表名 DROP COLUMN 你的列名;
```

然后在 `db.changelog-master.xml` 追加：

```xml
<include file="changes/你的文件名.sql" relativeToChangelogFile="true"/>
```

---

## 5. 常见问题

### Q1：启动报「未解析到任何 changeset」

- master 是否用 **XML** 且 include 路径正确
- SQL 是否有 `--liquibase formatted sql`
- `--changeset` 是否为 **`author:id`** 格式

### Q2：`DATABASECHANGELOG` 没有记录但表结构变了

- 不要手工改表后指望 Liquibase 补记录；应走 changeset 或 `changelogSync`（需 DBA/负责人评估）
- 以前若存在绕过 Liquibase 的补 DDL 逻辑，不会写 `DATABASECHANGELOG`

### Q3：changeset 已执行，能否改 SQL 内容？

- **不能**改已执行 changeset 的 SQL 正文
- 需要新变更时 **新建文件 + 新 id**，在 master 追加 include

### Q4：draft 目录里的 SQL 什么时候生效？

1. 移到 `changes/`（或 `baseline/`）
2. 在 `db.changelog-master.xml` 增加 `<include>`
3. 重新编译并完整重启

---

## 6. 相关配置速查

```yaml
fs:
  liquibase:
    enabled: true
    fail-fast: true
    changelog: db/changelog/db.changelog-master.xml
    changelog-root: classpath:db/changelog/
    meta-table: database_init_meta
```

| 配置项 | 说明 |
|--------|------|
| `changelog` | master 入口 classpath 路径 |
| `meta-table` | 存 bundle MD5 的表，与 `baseline/init-meta.sql` 创建的表名一致 |
| `fail-fast` | 迁移/校验失败是否禁止启动 |

---

## 7. 验证清单（发版前）

- [ ] 新 SQL 已加入 `db.changelog-master.xml`
- [ ] 每个 changeset 为 `author:id` 格式且 id 全局唯一
- [ ] precondition 使用 `--precondition-sql-check`
- [ ] 本地启动日志：`已解析=N, 待执行=0, 缺失记录=0`
- [ ] `DATABASECHANGELOG` 有新记录
- [ ] 业务依赖的新列/索引在库中真实存在
