feat: HSM main/slave certificate comparison with batch SQL generation

- compare usercert_tab by subject raw bytes + certtype
- generate single annotated CSV report
- generate merge-<timestamp>.sql in current directory for DBA execution
- preserve GBK DN bytes via ATTACH + INSERT OR REPLACE / UPDATE
- compatible with sqlite3 3.6.20 and JDK 1.8
This commit is contained in:
cheney
2026-11-04 15:18:42 +08:00
commit fe28ddeaac
38 changed files with 4952 additions and 0 deletions
+577
View File
@@ -0,0 +1,577 @@
# HSM 主备数据库证书比对工具 需求文档
| 项目 | 内容 |
| --- | --- |
| 文档版本 | v0.4 |
| 编写日期 | 2026-11-04 |
| 状态 | 已更新(批量 SQL 方案) |
| 关联文档 | `设计文档.md` |
| 测试环境 | 172.1.41.139(CentOS 6.5 / JDK 1.8 / SQLite) |
| 测试数据 | `/app/cert-compare/main.db`(4 条)、`/app/cert-compare/slave.db`(15 条) |
---
## 1. 背景
HSM 系统使用 SQLite3 数据库保存用户证书,证书保存在 `usercert_tab` 表中,生产规模约 3000 张。主、备两套系统各自维护一份数据库,长期运行后可能出现:
- 主库中存在、从库中不存在的证书(主库多);
- 从库中存在、主库中不存在的证书(从库多);
- 两边都存在同一张证书(同一 DN + 证书类型),但 SN、有效期、状态等字段不一致;
- 同一 DN 下存在多张证书(例如双证),不能简单地按 DN 去重。
主备库中的证书都是业务需要的,不存在“多余证书”的概念。比对的目的不是删除数据,而是:
1. 找出主库多、从库多、字段不一致三类差异;
2. 由业务人员在报表中逐条确认合并后采用“主”还是“备”;
3. 以主库为基础,把从库多出的证书补进来,并按人工标注处理冲突,最终形成完整可用的主库数据。
目前依赖人工执行 `sqlite3` 命令核对,存在以下问题:
- 表中 `subject`/`issuer` 实际存储的是 **GBK/GB18030 字节**,而 SQLite 头部声明为 UTF-8,直接用 UTF-8 工具读取会出现乱码甚至错误合并;
- 证书身份不是单纯的 DN,表主键为 `(subject, certtype)`;
- 3000 条数据人工比对不可行,且容易遗漏;
- 合并时如果经过 Java `String` 转换,会破坏原始 GBK 字节,导致写回的数据无法被原系统正确查询。
因此需要一个可在生产环境直接运行的 Java CLI 工具,支持交互式操作和脚本化调用,完成主备库证书比对,并依据人工标注生成在主库执行的批量 SQL 脚本。
---
## 2. 建设目标
1. 通过配置文件同时加载主库、从库两个 SQLite3 数据库。
2. 以主库为基准,准确识别三类差异:
- **主库多**:仅主库存在;
- **从库多**:仅从库存在;
- **不一致**:两边都有,但比较字段不同。
3. 提供 `set main/slave`、`compare`、`sql` 等交互命令。
4. `compare` 只输出**一份报表**,包含三类差异;汇总统计直接打印在控制台。
5. 报表预留“合并选择”字段,人工填写“主”或“备”。
6. `sql` 基于人工标注生成**在主库上执行的批量 SQL 脚本**:
- 以主库为基准;
- 从库多的证书全部补入;
- 不一致的证书按人工标注选择主库版本或从库版本;
- 脚本由 DBA 在主库执行,工具本身不直接写数据库;
- 从库仅以 `ATTACH` 只读引用,DN 中文按原始字节复制。
7. 不输出日志文件;运行信息、错误信息只打印到控制台。
8. 兼容生产环境:CentOS 6.5、glibc 2.12、JDK 1.8、无外网、无 Maven。
---
## 3. 范围
### 3.1 本期范围(In Scope)
- 仅处理 `usercert_tab` 表;
- 主库、从库两个数据库路径,以及 SQL 脚本输出目录的配置与切换;
- 证书比对与三类差异统计;
- 单份 CSV 报表(含人工合并选择列);
- 依据报表标注生成批量 SQL 脚本,由 DBA 在主库执行完成合并;
- 控制台汇总、错误提示、退出码;
- 交互式 Shell 与一次性命令两种使用方式;
- 在测试库上完成验收。
### 3.2 不在本期范围(Out of Scope)
- HSM 硬件、密钥、私钥的读取或操作;
- 证书签发、吊销、CRL、信任列表维护;
- `trustlist_tab`、`apetcert_tab`、`sym_key` 等其他表的合并(SQL 脚本只操作 `usercert_tab`);
- 修改现有数据库表结构或索引;
- Web UI、服务端常驻进程、定时任务;
- 日志文件、审计日志;
- 证书密码学有效性验证(签名链、私钥匹配等);
- 报表中除“主/备”之外的其他合并策略。
---
## 4. 术语
| 术语 | 说明 |
| --- | --- |
| 主库(main) | 基础数据库,合并时以它为基础 |
| 从库(slave) | 参与比对、提供差异证书的数据库 |
| 批量 SQL 脚本 | `sql` 生成的 `merge-<时间戳>.sql`,由 DBA 在主库执行完成合并 |
| DN / subject | 证书主体名称,表中为 `C=...,O=...,OU=...,CN=...` 形式的自定义顺序字符串,实际为 GBK 字节 |
| SN | 证书序列号,表中为十六进制字符串,可能带前导 0,逻辑上唯一 |
| certtype | 证书类型,表主键的一部分;同一 DN 可有多张不同 certtype 的证书(如双证) |
| 身份键 | 判断“同一张证书”的键:`subject 原始字节 + certtype` |
| 主库多 | 主库存在、从库不存在的证书 |
| 从库多 | 从库存在、主库不存在的证书 |
| 不一致 | 身份键相同,但 SN、有效期、状态等比较字段不同 |
| 一致 | 身份键相同且比较字段全部相同;只在汇总统计中计数,不进入报表 |
| 合并选择 | 报表中的人工标注列,取值“主”或“备” |
---
## 5. 用户与使用场景
| 角色 | 场景 |
| --- | --- |
| 运维人员 | 例行核对主备证书差异,导出报表 |
| 实施人员 | 数据迁移、容灾切换后核对并生成批量 SQL 脚本,交 DBA 在主库执行 |
| 研发/测试 | 定位双证、续期、状态差异问题 |
| 业务人员 | 在报表中逐条标注不一致证书采用主库还是从库版本 |
| 自动化脚本 | 执行 `compare` 生成报表,标注后执行 `sql` 生成批量脚本 |
---
## 6. 数据规则(已按测试库验证)
### 6.1 表结构
`usercert_tab` 主要字段:
| 字段 | 类型 | 说明 | 比对角色 |
| --- | --- | --- | --- |
| `organid` | varchar(32) | 机构标识 | 扩展字段 |
| `subject` | varchar(256) | DN,GBK/GB18030 字节 | **身份键**、展示字段 |
| `issuer` | varchar(256) | 颁发者,GBK/GB18030 字节 | 默认比较字段 |
| `sn` | varchar(64) | 证书序列号,十六进制字符串 | **唯一编号**、默认比较字段 |
| `pubkey_alg` | int | 公钥算法 | 扩展字段 |
| `pubkey` | varchar(1032) | 公钥 | 扩展字段(比较哈希) |
| `sign_alg` | int | 签名算法 | 扩展字段 |
| `signinfo` | varchar(512) | 签名信息 | 扩展字段(比较哈希) |
| `not_before` | varchar(14) | 生效时间 `yyyyMMddHHmmss` | 默认比较字段 |
| `not_after` | varchar(14) | 失效时间 `yyyyMMddHHmmss` | 默认比较字段 |
| `certfile` | varchar(2048) | PEM 证书内容 | 扩展字段(比较哈希) |
| `certtype` | smallint | 证书类型 | **身份键** |
| `opertime` | varchar(14) | 操作时间 | 扩展字段 |
| `stat` | smallint | 状态 | 默认比较字段 |
主键:`PRIMARY KEY (subject, certtype)`。
> 重要:不能只用 DN 判断同一张证书。测试库中 `sge` 机构同一 DN 存在 `certtype=0` 和 `certtype=2` 两张证书,若按 DN 去重会误判。
### 6.2 编码规则
- SQLite `PRAGMA encoding` 返回 `UTF-8`,但 `subject`、`issuer` 等字段实际写入的是 **GBK/GB18030 字节**;
- 使用 JDBC `getString()` 读取会把非法 UTF-8 字节替换为 `U+FFFD`,**严禁用于比对和回写**;
- 必须通过 `hex(col)` 或 `CAST(col AS BLOB)` 获取原始字节;
- 展示时按 GB18030 解码;解码失败时显示十六进制和转义字符;
- 合并写入必须使用 SQLite 内部 `ATTACH + INSERT ... SELECT`,避免 Java 字符串编码转换。
### 6.3 SN 规则
- 测试库中 SN 均为十六进制字符串,且与证书 DER 序列号一致;
- 存在前导 0,例如表中 `01971EE53B08`,DER 序列号为 `1971EE53B08`;
- 比对时默认归一化:去首尾空白、转大写、去前导 0;展示和写回保留原始值;
- SN 在单个库内逻辑唯一;跨库同 SN 视为同一序列号,但仍以身份键定位证书。
### 6.4 时间规则
- `not_before`、`not_after` 为 14 位 `yyyyMMddHHmmss` 本地时间字符串;
- 测试库全部记录与证书 DER 有效期一致;
- 比对按原始字符串比较,同时校验格式与 `not_before <= not_after`;
- 可选出报告:已过期、未生效、有效,按当前系统时间计算。
### 6.5 certtype 说明
测试库中观察到 `certtype` 取值 `0/1/2/4/5`,同一 DN 可出现多个值。本期不硬编码业务含义,报表按数值展示;后续可通过配置映射为中文名称。
---
## 7. 功能需求
### FR-01 配置加载
- 支持 UTF-8 编码的 `config.properties`;
- 至少包含:主库路径、从库路径、文本编码、DN 匹配方式、SN 归一化方式、报表目录;
- 命令行参数优先于配置文件;
- 配置文件不存在时使用内置默认值,但执行 `compare`/`sql` 前必须已有有效配置;
- 配置错误时给出明确中文提示。
**验收**:无参数启动进入交互模式;配置中的路径正确显示;路径错误时提示“文件不存在/不可读”,且不覆盖原配置。
### FR-02 交互式 Shell
- 启动后显示 `cert-compare>` 提示符;
- 支持命令:`set`、`status`、`compare`、`sql`、`help`、`quit`/`exit`;
- 命令大小写不敏感;
- 支持单引号/双引号包裹含空格的参数;
- 空行忽略;`Ctrl-D` 等同于 `quit`;
- `TERM=dumb` 或非 TTY 时不输出 ANSI 颜色控制字符;
- 单条命令失败不退出 Shell,仅打印错误。
**验收**:连续执行多条命令,错误后仍可继续;`quit` 正常退出并返回退出码 0。
### FR-03 set main
```text
set main <path>
```
- 校验文件存在、可读、是合法 SQLite 文件、包含 `usercert_tab` 表、必需字段齐全;
- 成功时输出:绝对路径、文件大小、证书总数;
- 仅保存路径,不长期持有数据库连接,避免锁库;
- 无参数执行 `set main` 时显示当前主库路径与状态。
### FR-04 set slave
同 FR-03,用于设置从库路径。
### FR-05 SQL 脚本输出位置
- 不提供单独的输出目录设置命令;
- `sql` 生成的脚本直接写入**运行时的当前工作目录**;
- 脚本文件名固定为 `merge-<yyyyMMdd-HHmmss>.sql`,每次生成新文件,不覆盖历史脚本;
- 启动脚本 `cert-compare.sh` 会先 `cd` 到程序目录,因此在服务器上脚本默认落在 `/app/cert-compare/`。
### FR-06 status
显示当前会话状态:
- 主库、从库的绝对路径、是否存在、文件大小、证书数量;
- 当前工作目录(即 SQL 脚本输出位置);
- 当前编码、DN 匹配方式、SN 归一化方式;
- 主库与从库表结构是否一致;
- 最近一次 `compare` 生成的报表路径;
- 最近一次 `sql` 生成的脚本路径。
### FR-07 compare 分类
对两库全部证书按身份键分类:
| 分类 | 判定 | 是否进入报表 |
| --- | --- | --- |
| 主库多(ONLY_MAIN) | 主库有、从库无 | 是 |
| 从库多(ONLY_SLAVE) | 从库有、主库无 | 是 |
| 不一致(DIFF) | 两边都有,至少一个默认比较字段不同 | 是 |
| 一致(EQUAL) | 两边都有,默认比较字段全部相同 | 否,仅汇总计数 |
默认比较字段:`sn`(归一化后)、`issuer`(原始字节)、`not_before`、`not_after`、`stat`。
`compare` 不做过滤、不做查询、不接收业务筛选参数;所有差异一次性写入同一份报表,由使用者在报表中自行过滤。
**验收**:测试库 `main.db`(4 条)与 `slave.db`(15 条)比对结果为:一致 0、主库多 1、从库多 12、不一致 3;报表共 16 条差异记录。
### FR-08 compare 报表
- 只生成**一份 CSV 报表**,不再拆分为多个文件;
- 文件名:`compare-report-<yyyyMMdd-HHmmss>.csv`;
- 控制台只打印汇总统计和报表路径,不打印逐条明细;
- CSV 使用 UTF-8 + BOM,保证 Excel 打开中文不乱码;
- 报表只包含主库多、从库多、不一致三类记录,不包含一致记录;
- 报表至少包含以下列:
| 列 | 说明 |
| --- | --- |
| 序号 | 报表内序号,便于人工沟通 |
| 类型 | 主库多 / 从库多 / 不一致 |
| DN | GB18030 解码后的 DN,便于阅读 |
| DN_HEX | 原始 DN 字节十六进制,带 `HEX:` 前缀,用于精确匹配 |
| CN | 从 DN 提取的 CN,仅展示 |
| CERTTYPE | 证书类型 |
| 主库SN / 从库SN | 两边 SN,不存在的一侧留空 |
| 主库生效 / 主库失效 | 主库有效期,不存在的一侧留空 |
| 从库生效 / 从库失效 | 从库有效期,不存在的一侧留空 |
| 主库状态 / 从库状态 | 两边 `stat`,不存在的一侧留空 |
| 差异字段 | 不一致时列出差异字段,如 `sn;not_after` |
| 合并选择 | **最后一列,人工填写:主 / 备** |
- `合并选择` 固定放在最后一列,便于 Excel 中直接拉到最右侧填写;
- 报表不再包含“备注”列,数据异常信息改由 `compare` 在控制台“提示”中输出;
- `DN_HEX` 统一加 `HEX:` 前缀,避免 Excel 把长十六进制转换成科学计数法;`sql` 读取时自动去掉前缀;
- 默认不输出 `certfile`、`pubkey`、`signinfo` 等大字段;
- 报表生成后,控制台打印报表绝对路径和汇总统计。
### FR-09 人工标注规则
- **主库多**:只有主库记录,`合并选择` 默认填 `主`,且只允许 `主`;
- **从库多**:只有从库记录,`合并选择` 默认填 `备`,且只允许 `备`;
- **不一致**:`合并选择` 默认留空,必须人工填写 `主` 或 `备`;
- 空白或非法值(非“主/备”)在 `sql` 时视为错误,拒绝生成脚本并提示具体行号;
- 人工只能修改 `合并选择` 列,不应修改 DN、DN_HEX、CERTTYPE 等定位列;
- 报表保存为 UTF-8 或 GBK/GB18030 均可,`sql` 读取时自动识别。
### FR-10 sql 生成计划
`sql` 读取已人工标注的报表,校验后生成批量 SQL 脚本,并先打印计划:
- 主库证书数、从库证书数;
- 一致、主库多、从库多、不一致数量;
- 写入记录数(从库多 + 不一致选备);
- 不一致中选择“主”的数量、选择“备”的数量;
- 执行后主库应有的记录数;
- 从库路径(脚本以 `ATTACH` 只读引用)与脚本输出路径;
- 控制台给出“备份主库 + `sqlite3 主库 < 脚本`”的执行命令。
`sql` 只写脚本文件,不连接主库执行、不修改任何数据库。
### FR-11 批量 SQL 脚本内容
- 脚本输出到运行时的当前工作目录,文件名 `merge-<yyyyMMdd-HHmmss>.sql`;
- 脚本本身不携带证书数据,而是 `ATTACH DATABASE '<从库>' AS cert_src` 后按身份键复制;
- **从库多**的证书:`INSERT OR REPLACE ... SELECT ... FROM cert_src.usercert_tab WHERE ...`;
- **不一致 + 选择主**:不产生语句,主库版本自然保留;
- **不一致 + 选择备**:用从库版本整行替换主库版本;
- **主库多**:不产生语句,主库版本自然保留;
- 所有语句按身份键(`hex(subject)` + `certtype`)定位,**不出现 DN 文本**,从库记录由 SQLite 内部按原始字节复制,DN 中的 GBK 中文不会被解码或重新编码;
- 脚本使用 `BEGIN IMMEDIATE` / `COMMIT` 包裹,可重复执行且结果一致(幂等);
- 脚本末尾输出 `select 'rows', count(*)` 与 `PRAGMA integrity_check` 供执行后自检;
- 脚本为 UTF-8(无 BOM),SQL 语句与字符串字面量全部为 ASCII,仅注释包含中文。
### FR-12 sql 报表校验
生成脚本前必须校验:
1. 报表存在、可解析、列头完整;
2. 报表中的差异记录与当前两库重新比对的结果一致(差异数量、身份键、类型);报表中的 SN/有效期等字段仅供人工阅读,脚本数据始终取自当前数据库;
3. 每条记录都有合法的“合并选择”;
4. 报表中的差异键集合、类型与当前比对一致;差异集合发生变化时视为报表过期,必须重新 `compare`。
校验失败时拒绝生成脚本,提示“报表已过期或标注不完整,请重新执行 compare 并标注”,不产生任何脚本。
### FR-13 脚本执行与结果校验
脚本由 DBA 在主库上执行:
```bash
cp -p /app/cert-compare/main.db /app/cert-compare/main.db.bak-$(date +%Y%m%d-%H%M%S)
sqlite3 /app/cert-compare/main.db < /app/cert-compare/merge-20261104-213307.sql
```
执行后应满足:
1. 脚本输出 `rows` 等于“主库数量 + 从库多数量”;
2. `PRAGMA integrity_check` 返回 `ok`;
3. 选择“主”的不一致记录保持主库版本;
4. 选择“备”的不一致记录等于从库版本;
5. 从库多记录全部存在,DN/SN/时间等字段与从库一致;
6. 从库文件不被修改(脚本只读引用);
7. 主库 DN 中文按原始字节写入,`hex(subject)` 与来源库一致。
### FR-14 错误与退出码
- 不生成任何日志文件;
- 运行信息、警告、错误只输出到控制台;
- `--debug` 时可在控制台输出详细堆栈;
- 退出码:
| 退出码 | 含义 |
| --- | --- |
| 0 | 成功(交互模式正常退出) |
| 1 | 未预期内部错误 |
| 2 | 参数/配置错误 |
| 4 | 数据库打开失败、表结构不兼容、完整性校验失败 |
| 5 | 报表过期、标注非法、SQL 脚本生成/写入失败 |
### FR-15 一次性命令模式
支持不进入交互 Shell,直接执行:
```bash
# 生成报表
java -Dfile.encoding=UTF-8 -jar cert-compare.jar compare \
--config /app/cert-compare/config.properties
# 人工标注报表后生成批量 SQL
java -Dfile.encoding=UTF-8 -jar cert-compare.jar sql \
--config /app/cert-compare/config.properties \
--report /app/cert-compare/reports/compare-report-20261104-213307-annotated.csv
# 由 DBA 在主库执行生成的脚本
sqlite3 /app/cert-compare/main.db < /app/cert-compare/merge-20261104-213307.sql
```
一次性模式与交互模式共用同一套比对、报表、SQL 生成引擎,结果必须一致。
### FR-16 help
- `help` 输出命令列表与示例;
- `help compare`、`help sql` 输出用法说明;
- 未知命令给出提示,不退出 Shell。
---
## 8. 非功能需求
| 编号 | 需求 |
| --- | --- |
| NFR-01 兼容性 | 可在 CentOS 6.5 / glibc 2.12 / JDK 1.8 上运行;无外网依赖;不要求服务器安装 Maven |
| NFR-02 性能 | 3000 张证书:`compare` ≤ 3 秒,`sql` ≤ 3 秒;生成的脚本可在 sqlite3 3.6.20 上执行;JVM 内存 ≤ 256MB |
| NFR-03 编码正确性 | DN/issuer 原始字节零损失;合并前后十六进制一致;脚本中不出现 DN 文本;禁止 `U+FFFD` 参与比较 |
| NFR-04 数据安全 | 工具默认只读源库;不直接修改任何数据库,只生成 SQL 脚本;不生成日志文件 |
| NFR-05 可靠性 | 脚本使用事务包裹且可重复执行;执行失败可按备份回滚;从库 SHA-256 不变 |
| NFR-06 易用性 | 中文提示;报表可直接用 Excel 标注;错误信息包含原因与处理建议 |
| NFR-07 可维护性 | 分层设计;核心逻辑有单元测试;不修改现有表结构 |
| NFR-08 可部署性 | 单个可执行 fat jar + 配置文件 + 启动脚本即可部署 |
| NFR-09 报表兼容性 | 报表读取兼容 UTF-8 BOM、UTF-8、GBK/GB18030 三种保存方式 |
---
## 9. 交互与输出示例
### 9.1 设置数据库
```text
cert-compare> set main /app/cert-compare/main.db
[OK] 主库: /app/cert-compare/main.db (459,776 字节, 4 张证书)
cert-compare> set slave /app/cert-compare/slave.db
[OK] 从库: /app/cert-compare/slave.db (459,776 字节, 15 张证书)
```
### 9.2 比对
```text
cert-compare> compare
主库: /app/cert-compare/main.db 证书数: 4
从库: /app/cert-compare/slave.db 证书数: 15
汇总:
一致 0
主库多 1
从库多 12
不一致 3
差异合计 16
报表:
/app/cert-compare/reports/compare-report-20261103-205500.csv
请按“合并选择”列标注:主 或 备,然后执行 sql 生成批量脚本。
```
### 9.3 报表示例(CSV)
```csv
序号,类型,DN,DN_HEX,CN,CERTTYPE,主库SN,从库SN,主库生效,主库失效,从库生效,从库失效,主库状态,从库状态,差异字段,合并选择
1,从库多,"C=CN,ST=北京,L=北京,O=信雅达,OU=双证,CN=172.1.40.188","HEX:433D434E2C...",172.1.40.188,0,,01971EE53B08,,,20250529160000,20260529160000,,0,,备
2,主库多,"C=cn,O=CFCA OCA1 SM2,OU=Local RA OCA1,OU=Business Customers,CN=041@...@陈超@...","HEX:433D636E2C...",041@...@陈超@...,1,1177482454,,20260806025132,20270806025132,,,,0,,主
14,不一致,CN=diff-sn-01,"HEX:434E3D646966662D736E2D3031",diff-sn-01,0,0A0B0C01,0A0B0C11,20260101000000,20301231235959,20260101000000,20301231235959,0,0,sn,主
15,不一致,CN=diff-stat-03,"HEX:434E3D646966662D737461742D3033",diff-stat-03,5,0A0B0C03,0A0B0C03,20260101000000,20301231235959,20260101000000,20301231235959,0,1,stat,主
16,不一致,CN=diff-time-02,"HEX:434E3D646966662D74696D652D3032",diff-time-02,2,0A0B0C02,0A0B0C02,20260101000000,20301231235959,20260101000000,20311231235959,0,0,not_after,备
```
### 9.4 生成批量 SQL
```text
cert-compare> sql --report /app/cert-compare/reports/compare-report-20261104-213307-annotated.csv
SQL 生成计划:
主库证书数 4
从库证书数 15
一致 0
主库多 1
从库多 12
不一致 3
写入(从库多+选备) 13
不一致选主 2
不一致选备 1
执行后主库记录数 16
从库(只读引用) /app/cert-compare/slave.db
SQL 文件 /app/cert-compare/merge-20261104-213307.sql
请在主库上执行(执行前先备份):
cp -p /app/cert-compare/main.db /app/cert-compare/main.db.bak-$(date +%Y%m%d-%H%M%S)
sqlite3 /app/cert-compare/main.db < /app/cert-compare/merge-20261104-213307.sql
执行后主库记录数应为 16 条;脚本末尾会输出 rows 与 integrity_check 结果。
```
执行脚本后:
```text
rows|16
ok
```
---
## 10. 验收标准
| 编号 | 场景 | 期望结果 |
| --- | --- | --- |
| AC-01 | `set main/slave` | 路径正确显示;主库 4 条、从库 15 条;当前目录可写 |
| AC-02 | `compare` | 一致 0、主库多 1、从库多 12、不一致 3;控制台只打印汇总 |
| AC-03 | 报表 | 只生成一份 CSV,含 16 条差异记录;“合并选择”为最后一列,无“备注”列 |
| AC-04 | 默认标注 | 主库多默认“主”,从库多默认“备”,不一致留空 |
| AC-05 | 标注后 sql | 生成 1 个 SQL 脚本;1 条 INSERT OR REPLACE(13 个身份键);无 UPDATE 语句 |
| AC-06 | 不一致选主 | 执行脚本后该身份键字段与主库一致 |
| AC-07 | 不一致选备 | 执行脚本后该身份键字段与从库一致 |
| AC-08 | 报表编码 | UTF-8 BOM 报表 Excel 打开中文正常;GBK 保存后 sql 仍可读取 |
| AC-09 | 报表过期 | 数据库变化后使用旧报表生成脚本,拒绝执行并提示重新 compare |
| AC-10 | 标注非法 | 不一致记录留空或填“其他”,生成脚本被拒绝并提示行号 |
| AC-11 | 同一 DN 不同 certtype | `sge` 的 certtype=0 与 2 均保留,不互相覆盖 |
| AC-12 | 字节保持 | 执行脚本后每条 `hex(subject)` 与来源库一致,`typeof(subject)='text'` |
| AC-13 | 源库只读 | 生成脚本前后两源文件 SHA-256 不变;执行后从库 SHA-256 不变 |
| AC-14 | 无日志文件 | 运行过程不生成 `*.log`,信息仅输出到控制台 |
| AC-15 | 3000 条模拟数据 | compare ≤ 3s,sql ≤ 3s,内存 ≤ 256MB |
| AC-16 | 脚本兼容性 | 脚本可在服务器 sqlite3 3.6.20 上一次执行成功,重复执行结果一致 |
---
## 11. 假设、约束与待确认事项
### 11.1 已确认
- `set main` / `set slave` 设置主库、从库路径;
- `sql` 生成的脚本直接写入运行时的当前工作目录;
- `sql` 只生成脚本,不直接修改任何数据库;脚本由 DBA 在主库执行;
- 脚本通过 `ATTACH` 从从库按原始字节复制,DN 中文不经过字符集转换;
- 证书身份键为 **DN + certtype**,SN 作为唯一编号参与比较;
- 主库多、从库多、不一致三类差异均进入同一份报表;
- 合并选择只有“主”“备”两种;
- 不生成日志文件。
### 11.2 假设
- `usercert_tab` 结构在生产与测试环境一致;
- 证书数量约 3000,单库文件规模在数十 MB 以内;
- 生产比对可在业务低峰或维护窗口执行,避免与 HSM 应用并发写冲突;
- 服务器 `/tmp` 可写且允许执行(sqlite-jdbc 需解压本地库);
- 业务人员使用 Excel 或文本编辑器修改报表的“合并选择”列。
### 11.3 待确认
1. `certtype` 各数值的业务含义(0/1/2/4/5),是否需要报表中文映射;
2. 生产环境是否允许停应用/只读挂载,或必须在线比对;
3. 是否需要同时核对 `trustlist_tab`、`apetcert_tab`(本期不做);
4. SQL 脚本文件命名与保留策略(当前为 `merge-<时间戳>.sql`,不覆盖历史脚本);
5. SN 前导 0 归一化是否与现网查询逻辑一致;
6. DN 是否需要忽略属性大小写/顺序的“近似匹配”(本期默认严格字节匹配);
7. 主库多/从库多的“合并选择”是否允许改成其他值(本期固定为主/备,确保不丢证书)。
---
## 12. 里程碑
| 阶段 | 内容 | 交付物 |
| --- | --- | --- |
| M1 | 环境验证、需求与设计 | 本文件、`设计文档.md` |
| M2 | 工程骨架、配置、set/status | 可启动的 CLI、配置加载、库校验 |
| M3 | compare 引擎与单份报表 | 三类差异、控制台汇总、CSV |
| M4 | 报表读取、标注校验、批量 SQL 生成 | SQL 脚本、脚本执行验证 |
| M5 | 测试与部署 | 单元/集成测试、3000 条压测、部署脚本、验收记录 |
---
## 13. 附:测试库现状摘要
| 项目 | main.db | slave.db |
| --- | --- | --- |
| 文件大小 | 459,776 字节 | 459,776 字节 |
| `usercert_tab` 记录数 | 4 | 15 |
| certtype 分布 | 0:1、1:1、2:1、5:1 | 0:5、2:3、4:1、5:6 |
| 重复身份键 | 无 | 无 |
| 同一 DN 多证书 | 无 | 有(`sge`,certtype 0/2) |
| 重复 SN | 无 | 无 |
| 日期格式异常 | 无 | 无 |
| 完整性检查 | ok | ok |
其中 3 条为专门构造的“不一致”测试数据(主备同 DN + certtype,SN/有效期/状态不同):
| DN | CERTTYPE | 差异 | 主库 | 从库 |
| --- | --- | --- | --- | --- |
| `CN=diff-sn-01` | 0 | SN | `0A0B0C01` | `0A0B0C11` |
| `CN=diff-time-02` | 2 | 失效时间 | `20301231235959` | `20311231235959` |
| `CN=diff-stat-03` | 5 | 状态 | `0` | `1` |
按本期规则,测试库的预期比对结论为:
- 主库多:**1 张**;
- 从库多:**12 张**;
- 不一致:**3 张**;
- 一致:0 张;
- 报表差异记录:16 条;
- 执行 SQL 后(示例标注:2 条选主、1 条选备):**16 张**。