diff --git a/db_config.conf.template b/db_config.conf.template deleted file mode 100644 index e18b746..0000000 --- a/db_config.conf.template +++ /dev/null @@ -1,32 +0,0 @@ -# 数据库迁移配置文件模板 -# 使用方法:复制此文件为 db_config.conf 并填写实际值 - -# 源数据库配置(开发环境) -SOURCE_DB_HOST=your_source_host -SOURCE_DB_PORT=3306 -SOURCE_DB_USER=your_source_user -SOURCE_DB_PASS=your_source_password -SOURCE_DB_NAME=ccdi - -# 生产环境数据库配置 -PROD_DB_HOST=your_production_host -PROD_DB_PORT=3306 -PROD_DB_USER=your_production_user -PROD_DB_PASS=your_production_password -PROD_DB_NAME=ccdi - -# 测试环境数据库配置(可选) -TEST_DB_HOST=your_test_host -TEST_DB_PORT=3306 -TEST_DB_USER=your_test_user -TEST_DB_PASS=your_test_password -TEST_DB_NAME=ccdi - -# 导出文件配置 -BACKUP_DIR=doc/database/backup -STRUCTURE_FILE=ccdi_structure.sql -DATA_FILE=ccdi_data.sql - -# mysqldump 参数配置 -CHARACTER_SET=utf8mb4 -MAX_ALLOWED_PACKET=512M diff --git a/doc/database/backup/ccdi_data.sql b/doc/database/backup/ccdi_data.sql index 3367488..ea07c58 100644 --- a/doc/database/backup/ccdi_data.sql +++ b/doc/database/backup/ccdi_data.sql @@ -1,5 +1,5 @@ -- CCDI 数据库数据 --- 导出时间: 2026-02-28 15:23:04 +-- 导出时间: 2026-02-28 15:26:26 -- 源数据库: 116.62.17.81:3306/ccdi -- 字符集: utf8mb4 @@ -445,6 +445,6 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2026-02-28 15:23:04 +-- Dump completed on 2026-02-28 15:26:26 SET FOREIGN_KEY_CHECKS=1; diff --git a/doc/database/backup/ccdi_structure.sql b/doc/database/backup/ccdi_structure.sql index 9427914..3debedb 100644 --- a/doc/database/backup/ccdi_structure.sql +++ b/doc/database/backup/ccdi_structure.sql @@ -1,5 +1,5 @@ -- CCDI 数据库表结构 --- 导出时间: 2026-02-28 15:22:54 +-- 导出时间: 2026-02-28 15:26:16 -- 源数据库: 116.62.17.81:3306/ccdi -- 字符集: utf8mb4 @@ -1092,4 +1092,4 @@ CREATE TABLE `sys_user_role` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2026-02-28 15:22:54 +-- Dump completed on 2026-02-28 15:26:16 diff --git a/doc/test-scripts/2026-02-28-frontend-test-report.md b/doc/test-scripts/2026-02-28-frontend-test-report.md new file mode 100644 index 0000000..37410c0 --- /dev/null +++ b/doc/test-scripts/2026-02-28-frontend-test-report.md @@ -0,0 +1,308 @@ +# 前端功能测试报告 + +## 测试概述 + +**测试日期**: 2026-02-28 +**测试人员**: Claude Code +**测试环境**: +- 后端: http://localhost:8080 +- 前端: http://localhost:84 +- 浏览器: Chrome 145.0.0.0 +- 测试账号: admin/admin123 + +## 测试目标 + +验证项目管理页面状态统计数字显示正确,并在用户交互(搜索、分页、状态切换)过程中保持稳定。 + +## 测试场景 + +### ✅ 场景 1: 页面初始加载 + +**操作步骤**: +1. 访问前端应用 http://localhost:84 +2. 使用 admin/admin123 登录系统 +3. 导航到"初核项目管理"页面 + +**预期结果**: +- 页面正常加载 +- 标签页显示正确的统计数字 +- 两个 API 请求成功(list 和 statusCounts) + +**实际结果**: ✅ **通过** + +**验证数据**: +- 标签页统计: + - 全部项目(29) ✅ + - 进行中(27) ✅ + - 已完成(1) ✅ + - 已归档(1) ✅ +- 列表显示: 共 29 条 ✅ +- API 请求: + - `/ccdi/project/list?pageNum=1&pageSize=10` → 200 OK + - `/ccdi/project/statusCounts` → 200 OK + +**响应数据验证**: +```json +// list 接口响应 +{ + "total": 29, + "rows": [ ...10条记录... ], + "code": 200, + "msg": "查询成功" +} + +// statusCounts 接口响应 +{ + "msg": "操作成功", + "code": 200, + "data": { + "all": 29, + "status0": 27, + "status1": 1, + "status2": 1 + } +} +``` + +--- + +### ✅ 场景 2: 搜索功能 + +**操作步骤**: +1. 在搜索框输入 "测试4" +2. 按回车键触发搜索 + +**预期结果**: +- 列表只显示匹配的项目 +- 标签页数字保持不变(显示总数) + +**实际结果**: ✅ **通过** + +**验证数据**: +- 搜索结果: 1 条(测试4) ✅ +- 标签页统计(保持不变): + - 全部项目(29) ✅ + - 进行中(27) ✅ + - 已完成(1) ✅ + - 已归档(1) ✅ +- 分页显示: 共 1 条 ✅ + +**API 请求验证**: +``` +GET /ccdi/project/list?pageNum=1&pageSize=10&projectName=测试4 → 200 OK +``` + +--- + +### ✅ 场景 3: 分页功能 + +**操作步骤**: +1. 清空搜索框,刷新页面恢复初始状态 +2. 点击分页组件的"2"按钮,切换到第 2 页 + +**预期结果**: +- 列表切换到第 2 页数据 +- 标签页数字保持不变 + +**实际结果**: ✅ **通过** + +**验证数据**: +- 当前页码: 第 2 页 ✅ +- 标签页统计(保持不变): + - 全部项目(29) ✅ + - 进行中(27) ✅ + - 已完成(1) ✅ + - 已归档(1) ✅ +- 分页显示: 共 29 条 ✅ + +**API 请求验证**: +``` +GET /ccdi/project/list?pageNum=2&pageSize=10 → 200 OK +GET /ccdi/project/statusCounts → 200 OK +``` + +--- + +### ✅ 场景 4: 状态切换功能 + +**操作步骤**: +1. 点击"进行中"标签 + +**预期结果**: +- 列表只显示"进行中"状态的项目 +- 标签页数字保持不变(仍显示总数) + +**实际结果**: ✅ **通过** + +**验证数据**: +- 列表过滤: 所有项目状态都是"进行中" ✅ +- 标签页统计(保持不变): + - 全部项目(29) ✅ + - 进行中(27) ✅ + - 已完成(1) ✅ + - 已归档(1) ✅ +- 分页显示: 共 27 条(正确反映当前状态的项目数) ✅ + +**API 请求验证**: +``` +GET /ccdi/project/list?pageNum=1&pageSize=10&status=0 → 200 OK +GET /ccdi/project/statusCounts → 200 OK +``` + +**响应数据验证**: +```json +// status=0 过滤后的列表 +{ + "total": 27, + "rows": [ + {"projectId": 31, "projectName": "测试123", "status": "0", ...}, + {"projectId": 23, "projectName": "测试23", "status": "0", ...}, + ... + ], + "code": 200, + "msg": "查询成功" +} + +// 状态统计(始终返回总数) +{ + "msg": "操作成功", + "code": 200, + "data": { + "all": 29, + "status0": 27, + "status1": 1, + "status2": 1 + } +} +``` + +--- + +### ✅ 场景 5: 浏览器控制台检查 + +**操作步骤**: +1. 打开浏览器开发者工具的 Console 标签 + +**预期结果**: +- 没有 JavaScript 错误 +- 看到两个 API 请求成功 + +**实际结果**: ✅ **通过** + +**控制台消息**: +- ✅ 没有 JavaScript 错误 +- ⚠️ 1 个警告: "A form field element should have an id or name attribute" (不影响功能) + +--- + +## 网络请求统计 + +**总请求数**: 40 个 + +**关键 API 请求**: +1. 初始加载: + - `/ccdi/project/list?pageNum=1&pageSize=10` → 200 OK + - `/ccdi/project/statusCounts` → 200 OK + +2. 搜索功能: + - `/ccdi/project/list?pageNum=1&pageSize=10&projectName=测试4` → 200 OK + +3. 分页功能: + - `/ccdi/project/list?pageNum=2&pageSize=10` → 200 OK + - `/ccdi/project/statusCounts` → 200 OK + +4. 状态切换: + - `/ccdi/project/list?pageNum=1&pageSize=10&status=0` → 200 OK + - `/ccdi/project/statusCounts` → 200 OK + +**所有请求状态**: ✅ 全部成功(200 OK) + +--- + +## 核心修复验证 + +### 问题回顾 + +**原始问题**: 标签页数字随分页变化,不稳定 + +**根本原因**: 前端使用列表响应的 total 字段来更新标签页数字,导致搜索/分页/过滤时数字会变化 + +**解决方案**: +1. 后端新增 `/statusCounts` 接口,始终返回所有状态的总数 +2. 前端在每次加载时并行请求 list 和 statusCounts +3. 标签页数字只使用 statusCounts 的数据,不受列表过滤影响 + +### 修复效果验证 + +✅ **搜索时**: 标签页数字保持 29/27/1/1 不变 +✅ **分页时**: 标签页数字保持 29/27/1/1 不变 +✅ **状态切换时**: 标签页数字保持 29/27/1/1 不变 + +--- + +## 测试结论 + +### ✅ 所有测试场景通过 + +| 测试场景 | 状态 | 备注 | +|---------|------|------| +| 页面初始加载 | ✅ 通过 | 标签页数字正确显示 | +| 搜索功能 | ✅ 通过 | 数字保持稳定 | +| 分页功能 | ✅ 通过 | 数字保持稳定 | +| 状态切换功能 | ✅ 通过 | 数字保持稳定 | +| 浏览器控制台 | ✅ 通过 | 无 JavaScript 错误 | + +### 关键指标 + +- ✅ **功能正确性**: 100% 通过 +- ✅ **数据一致性**: 标签页数字在所有操作中保持稳定 +- ✅ **用户体验**: 符合预期,数字显示直观清晰 +- ✅ **性能**: API 请求并行执行,响应迅速 +- ✅ **代码质量**: 无 JavaScript 错误,警告不影响功能 + +### 建议 + +1. ✅ **功能完善**: 建议将此修复方案应用到其他类似的列表页面 +2. ⚠️ **警告处理**: 建议为搜索框添加 id 或 name 属性以消除控制台警告 +3. ✅ **文档更新**: 更新用户手册,说明标签页数字表示总数而非当前过滤结果 + +--- + +## 附录 + +### 测试环境信息 + +``` +操作系统: Windows 11 Pro 10.0.26200 +浏览器: Chrome 145.0.0.0 +后端服务: http://localhost:8080 +前端服务: http://localhost:84 +数据库: MySQL 8.2.0 +Java 版本: 17 +Spring Boot 版本: 3.5.8 +Vue.js 版本: 2.6.12 +``` + +### 相关文件 + +**后端**: +- `D:/ccdi/ccdi/ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/ProjectStatusCountsVO.java` +- `D:/ccdi/ccdi/ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiProjectService.java` +- `D:/ccdi/ccdi/ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiProjectServiceImpl.java` +- `D:/ccdi/ccdi/ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiProjectController.java` + +**前端**: +- `D:/ccdi/ccdi/ruoyi-ui/src/api/ccdiProject.js` +- `D:/ccdi/ccdi/ruoyi-ui/src/views/ccdiProject/index.vue` +- `D:/ccdi/ccdi/ruoyi-ui/src/views/ccdiProject/components/SearchBar.vue` + +### 测试执行时间 + +- 开始时间: 2026-02-28 09:46:48 +- 结束时间: 2026-02-28 09:50:00 +- 总耗时: 约 3 分钟 + +--- + +**测试人员签名**: Claude Code +**测试日期**: 2026-02-28 diff --git a/doc/对接流水分析/task.md b/doc/对接流水分析/task.md new file mode 100644 index 0000000..8aa3542 --- /dev/null +++ b/doc/对接流水分析/task.md @@ -0,0 +1,7 @@ +# 系统需要对接流水分析平台,调用流水分析平台的接口 +- 新建一个模块ccdi_lsfx,保存所有调用流水分析平台的代码 +- 创建一个用于发起http请求的工具类,使用RestTemplate +- 读取接口文档 D:\ccdi\ccdi\doc\对接流水分析\兰溪-流水分析对接.docx,分析每个接口的入参和返回值格式,封装入参和出参的对象 +- 在配置文档中添加每个接口对应的url配置 +- 在后端服务层中实现这些接口的调用,获取返回值 +- 创建一个控制层,可以调用这些接口,用于测试 diff --git a/doc/对接流水分析/兰溪-流水分析对接.docx b/doc/对接流水分析/兰溪-流水分析对接.docx new file mode 100644 index 0000000..932def3 Binary files /dev/null and b/doc/对接流水分析/兰溪-流水分析对接.docx differ diff --git a/doc/对接流水分析/兰溪-流水分析对接.md b/doc/对接流水分析/兰溪-流水分析对接.md new file mode 100644 index 0000000..66bbc92 --- /dev/null +++ b/doc/对接流水分析/兰溪-流水分析对接.md @@ -0,0 +1,561 @@ +# 兰溪-流水分析对接文档 + +## 概述 + +本文档描述与**见知现金流尽调系统**的对接接口,用于拉取银行流水数据。 + +--- + +## 1. 新建项目并获取Token + +### 1.1 接口请求地址 + +- **测试环境**: `http://158.234.196.5:82/c4c3/account/common/getToken` +- **请求方法**: POST + +### 1.2 请求参数说明 + +> 接口备注:第三方系统中,点击需要查看的项目向见知现金流尽调系统请求访问token,每个项目的token不同。现金流尽调系统根据 ProjectNo为唯一标识查找项目,如果对应的项目不存在则自动创建项目。注意token使用一次后即失效,再次访问项目需要重新申请。(支持拉取金综和行内流水) + +| 参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 | +|--------|--------|----------|----------|----------| +| projectNo | test-zjnx-1204 | String | 是 | 项目编号 | +| entityName | 浙江农信test1204 | String | 是 | 项目名称 | +| userId | test001 | String | 是 | 操作人员编号 | +| userName | 测试001 | String | 是 | 操作人员姓名 | +| appId | remote_app | String | 是 | 见知提供appId | +| appSecretCode | 6ee87a361f29234ad25d7893da9975a9 | String | 是 | 安全码 md5(projectNo + "_" + entityName + "_" + appSecret) | +| role | VIEWER | String | 否 | 人员角色(VIEWER:普通用户,READER:只读用户)默认值:VIEWER | +| orgCode | 800000 | String | 是 | 行社机构号 | +| entityId | ZJNX1234567890 | String | 否 | 企业统信码或个人身份证号 | +| xdRelatedPersons | [{"relatedPerson":"上海上水纯净水有限公司","relation":"董事长"}] | String | 否 | 信贷关联人信息 | +| jzDataDateId | 0 | String | 否 | 拉取指定日期推送过来的金综链流水,为0时标识不需要拉取金综链流水 | +| innerBSStartDateId | 0 | String | 否 | 拉取行内流水开始日期,0:不需要拉取行内流水。流水分析系统根据entityId到数仓中查询行内流水 | +| innerBSEndDateId | 0 | String | 否 | 拉取行内流水结束日期,0:不需要拉取行内流水。流水分析系统根据entityId到数仓中查询行内流水 | +| analysisType | -1 | String | 是 | 固定值 | +| departmentCode | 800111 | String | 是 | 客户经理所属营业部/分理处的机构编码 | + +### 1.3 返回参数说明 + +#### 成功响应 (200) + +| 参数名 | 示例值 | 参数类型 | 参数描述 | +|--------|--------|----------|----------| +| code | 200 | String | 返回码 | +| data.token | eyJ0eXAi... | String | token | +| data.projectId | 77 | Integer | 见知项目Id | +| data.projectNo | test-zjnx-1204 | String | 项目编号 | +| data.entityName | 浙江农信test1204 | String | 项目名称 | +| data.analysisType | 0 | Integer | 分析类型 | +| message | create.token.success | String | 消息 | +| status | 200 | String | 状态 | +| successResponse | true | Boolean | 成功标识 | + +#### 错误码说明 + +| 错误码 | 说明 | +|--------|------| +| 200 | 请求成功 | +| 40100 | 未知异常 | +| 40101 | appId错误 | +| 40102 | appSecretCode错误 | +| 40104 | 可使用项目次数为0,无法创建项目 | +| 40105 | 只读模式下无法新建项目 | +| 40106 | 错误的分析类型,不在规定的取值范围内 | +| 40107 | 当前系统不支持的分析类型 | +| 40108 | 当前用户所属行社无权限 | + +### 1.4 返回示例 + +```json +{ + "code": "200", + "data": { + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", + "projectId": 77, + "projectNo": "test-zjnx-1204", + "entityName": "浙江农信test1204", + "analysisType": 0 + }, + "message": "create.token.success", + "status": "200", + "successResponse": true +} +``` + +--- + +## 2. 上传文件接口 + +### 2.1 接口请求地址 + +- **测试环境**: `158.234.196.5:82/c4c3/watson/api/project/remoteUploadSplitFile` +- **请求方法**: POST +- **请求头**: `X-Xencio-Client-Id: c2017e8d105c435a96f86373635b6a09` + +### 2.2 请求参数说明 + +| 参数 | 类型 | 参数名称 | 是否必填 | 说明 | +|------|------|----------|----------|------| +| groupId | Int | 项目id | 是 | - | +| files | File | 上传的文件 | 是 | - | + +### 2.3 响应结果信息 + +| 字段 | 类型 | 备注 | +|------|------|------| +| code | String | 200成功,其他状态码失败 | +| data | Object | 列表 | +| accountName | - | 主体名称 | +| accountNo | - | 账号 | +| uploadFileName | - | 文件名称 | +| fileSize | - | 文件大小,单位Byte | +| status | - | 状态值 | +| uploadStatusDesc | - | 文件状态描述 | +| bank | - | 所属银行 | +| currency | - | 币种 | +| accountId | - | 账号id | +| logId | - | 文件id | + +> **注意**: `status`等于-5且`uploadStatusDesc`等于`data.wait.confirm.newaccount`表示当前流水文件上传后解析成功。反之则没有成功。 + +### 2.4 返回示例 + +```json +{ + "code": "200", + "data": { + "accountsOfLog": { + "13976": [ + { + "bank": "BSX", + "accountName": "", + "accountNo": "虞海良绍兴银行流水", + "currency": "CNY" + } + ] + }, + "uploadLogList": [ + { + "accountNoList": [], + "bankName": "BSX", + "dataTypeInfo": ["CSV", ","], + "downloadFileName": "虞海良绍兴银行流水.csv", + "enterpriseNameList": [], + "filePackageId": "14b13103010e4d32b5406c764cfe3644", + "fileSize": 46724, + "fileUploadBy": 448, + "fileUploadByUserName": "admin@support.com", + "fileUploadTime": "2025-03-12 18:53:29", + "leId": 10724, + "logId": 13976, + "logMeta": "{\"lostHeader\":[],\"balanceAmount\":true}", + "logType": "bankstatement", + "loginLeId": 10724, + "realBankName": "BSX", + "rows": 0, + "source": "http", + "status": -5, + "templateName": "BSX_T240925", + "totalRecords": 280, + "trxDateEndId": 20240905, + "trxDateStartId": 20230914, + "uploadFileName": "虞海良绍兴银行流水.csv", + "uploadStatusDesc": "data.wait.confirm.newaccount" + } + ], + "uploadStatus": 1 + }, + "status": "200", + "successResponse": true +} +``` + +--- + +## 3. 拉取行内流水接口 + +### 3.1 接口请求地址 + +- **测试环境**: `158.234.196.5:82/c4c3/watson/api/project/getJZFileOrZjrcuFile` +- **请求方法**: POST +- **请求头**: `X-Xencio-Client-Id: c2017e8d105c435a96f86373635b6a09` + +### 3.2 请求参数说明 + +| 参数 | 类型 | 参数名称 | 是否必填 | 说明 | +|------|------|----------|----------|------| +| groupId | Int | 项目id | 是 | - | +| customerNo | String | 客户身份证号 | 是 | - | +| dataChannelCode | String | 校验码 | 是 | 固定值: ZJRCU | +| requestDateId | Int | 发起请求的时间 | 是 | 当天请求时间 | +| dataStartDateId | Int | 拉取开始日期 | 是 | - | +| dataEndDateId | Int | 拉取结束日期 | 是 | - | +| uploadUserId | int | 柜员号 | 是 | - | + +### 3.3 响应结果信息 + +| 序号 | 字段 | 类型 | 备注 | +|------|------|------|------| +| 1 | code | String | 200成功,其他状态码失败 | +| 2 | data | Object | 列表 | + +### 3.4 返回示例(无行内流水) + +```json +{ + "code": "200", + "data": { + "code": "501014", + "message": "无行内流水文件" + }, + "status": "200", + "successResponse": true +} +``` + +--- + +## 4. 判断文件是否解析结束 + +### 4.1 接口请求地址 + +- **测试环境**: `http://158.234.196.5:82/c4c3/watson/api/project/upload/getpendings` +- **请求方法**: POST +- **请求头**: `X-Xencio-Client-Id: c2017e8d105c435a96f86373635b6a09` + +### 4.2 请求参数说明 + +| 参数 | 类型 | 参数名称 | 是否必填 | 说明 | +|------|------|----------|----------|------| +| groupId | Int | 项目id | 是 | - | +| inprogressList | String | 文件id | 是 | - | + +### 4.3 响应结果信息 + +| 序号 | 字段 | 类型 | 备注 | +|------|------|------|------| +| 1 | code | String | 200成功,其他状态码失败 | +| 2 | data | Object | 列表 | +| 3 | uploadFileName | - | 上传文件名称 | +| 4 | status | - | 文件解析后状态值 | +| 5 | uploadStatusDesc | - | 文件解析后状态描述 | +| 6 | parsing | - | 文件解析状态,true表示解析中,false表示解析结束 | + +> **注意**: 文件解析有个处理过程,`parsing`为false表示解析结束,可以轮询调用此接口,`status`等于-5且`uploadStatusDesc`等于`data.wait.confirm.newaccount`表示文件解析成功。 + +### 4.4 返回示例 + +```json +{ + "code": "200", + "data": { + "parsing": false, + "pendingList": [ + { + "accountNoList": [], + "bankName": "ZJRCU", + "dataTypeInfo": ["CSV", ","], + "downloadFileName": "230902199012261247_20260201_20260201_1772096608615.csv", + "enterpriseNameList": [], + "filePackageId": "cde6c7cf5cab48e8892f0c1c36b2aa7d", + "fileSize": 53101, + "fileUploadBy": 448, + "fileUploadByUserName": "admin@support.com", + "fileUploadTime": "2026-02-27 09:50:18", + "isSplit": 0, + "leId": 16210, + "logId": 19116, + "logMeta": "{\"lostHeader\":[],\"balanceAmount\":true}", + "logType": "bankstatement", + "loginLeId": 16210, + "lostHeader": [], + "realBankName": "ZJRCU", + "rows": 0, + "source": "http", + "status": -5, + "templateName": "ZJRCU_T251114", + "totalRecords": 131, + "trxDateEndId": 20240228, + "trxDateStartId": 20240201, + "uploadFileName": "230902199012261247_20260201_20260201_1772096608615.csv", + "uploadStatusDesc": "data.wait.confirm.newaccount" + } + ] + }, + "status": "200", + "successResponse": true +} +``` + +--- + +## 5. 生成尽调报告接口 + +### 5.1 接口请求地址 + +- **测试环境**: `158.234.196.5:82/c4c3/watson/api/project/confirmStageUploadLogs` +- **请求方法**: POST +- **请求头**: `X-Xencio-Client-Id: c2017e8d105c435a96f86373635b6a09` + +### 5.2 请求参数说明 + +| 参数 | 类型 | 参数名称 | 是否必填 | 说明 | +|------|------|----------|----------|------| +| groupId | Int | 项目id | 是 | - | +| logIds | Array | 文件id数组 | 是 | 上传几个文件就用几个 | +| userLogin | Int | 登录柜员号 | 是 | - | + +### 5.3 响应结果信息 + +| 序号 | 字段 | 类型 | 备注 | +|------|------|------|------| +| 1 | Code | String | 200成功,其他状态码失败 | +| 2 | Data | Object | 列表 | + +### 5.4 返回示例 + +```json +{ + "code": "200", + "data": { + "message": "upload.confirm.ok" + }, + "status": "200", + "successResponse": true +} +``` + +--- + +## 6. 判断尽调报告是否生成 + +### 6.1 接口请求地址 + +- **测试环境**: `158.234.196.5:82/c4c3/watson/api/project/upload/getallpendings?groupId=#{groupId}` +- **请求方法**: GET +- **请求头**: `X-Xencio-Client-Id: c2017e8d105c435a96f86373635b6a09` + +### 6.2 请求参数说明 + +| 参数 | 类型 | 参数名称 | 是否必填 | 说明 | +|------|------|----------|----------|------| +| groupId | Int | 项目id | 是 | - | + +### 6.3 响应结果信息 + +| 序号 | 字段 | 类型 | 备注 | +|------|------|------|------| +| 1 | code | String | 200成功,其他状态码失败 | +| 2 | data | Object | 列表 | + +> **注意**: 生成尽调报告有个处理过程,`pendingList`为[]表示处理结束,可以轮询调用此接口。 + +### 6.4 返回示例 + +```json +{ + "code": "200", + "data": { + "pendingList": [] + }, + "status": "200", + "successResponse": true +} +``` + +--- + +## 7. 获取流水列表并存储到兰溪本地 + +### 7.1 接口请求地址 + +- **测试环境**: `158.234.196.5:82/c4c3/watson/api/project/upload/getBankStatement` +- **请求方法**: POST +- **请求头**: `X-Xencio-Client-Id: c2017e8d105c435a96f86373635b6a09` + +### 7.2 请求参数说明 + +| 参数 | 类型 | 参数名称 | 是否必填 | 说明 | +|------|------|----------|----------|------| +| groupId | Int | 项目id | 是 | - | +| logId | Int | 文件id | 是 | - | +| pageNow | Int | 当前页码 | 是 | - | +| pageSize | Int | 查询条数 | 是 | - | + +### 7.3 响应结果信息 + +| 序号 | 字段 | 类型 | 备注 | +|------|------|------|------| +| 1 | code | String | 200成功,其他状态码失败 | +| 2 | data | Object | 列表 | +| 3 | bankStatementList | - | 流水列表 | +| 4 | pageable | - | 分页参数 | +| 5 | searchable | - | 查询参数 | + +### 7.4 流水字段说明 + +| 字段名 | 说明 | 示例值 | +|--------|------|--------| +| accountId | 账户ID | 0 | +| accountMaskNo | 账号 | 6228580199062321798 | +| accountingDate | 记账日期 | 2025-02-03 | +| accountingDateId | 记账日期ID | 20250203 | +| balanceAmount | 余额 | 85688.37 | +| bank | 银行代码 | AI | +| bankComments | 银行备注 | - | +| bankStatementId | 流水ID | 4585279 | +| catalogName | 交易名称 | 收单收入 | +| crAmount | 贷方金额 | 290 | +| currency | 币种 | CNY | +| customerAccountMaskNo | 客户账号 | 80100001471621000100 | +| customerAccountName | 客户账户名 | 系统内清算资金往来-全渠道收单平台 | +| customerName | 客户名称 | 系统内清算资金往来-全渠道收单平台 | +| drAmount | 借方金额 | 0 | +| leName | 企业名称 | 徐设华 | +| transAmount | 交易金额 | 290 | +| transFlag | 交易标志 | R | +| transTypeName | 交易名称 | 收单收入 | +| trxDate | 交易日期 | 2025-02-03 00:00:00 | +| userMemo | 用户备注 | 收单 | + +### 7.5 返回示例 + +```json +{ + "code": "200", + "data": { + "bankStatementList": [ + { + "accountId": 0, + "accountMaskNo": "6228580199062321798", + "accountingDate": "2025-02-03", + "accountingDateId": 20250203, + "archivingFlag": 0, + "attachments": 0, + "balanceAmount": 85688.37, + "bank": "AI", + "bankComments": "", + "bankStatementId": 4585279, + "bankTrxNumber": "", + "cashType": "", + "catalogName": "收单收入", + "commentsNum": 0, + "crAmount": 290, + "currency": "CNY", + "customNoteCount": 0, + "customerAccountMaskNo": "80100001471621000100", + "customerAccountName": "系统内清算资金往来-全渠道收单平台", + "customerId": 0, + "customerName": "系统内清算资金往来-全渠道收单平台", + "customerReference": "", + "downPaymentFlag": 0, + "drAmount": 0, + "hasCustomNote": 0, + "internalFlag": 0, + "isMarked": 0, + "leId": 16260, + "leName": "徐设华", + "sourceCatalogId": 405625, + "split": 0, + "subBankstatementId": 0, + "toDoFlag": 0, + "transAmount": 290, + "transFlag": "R", + "transTypeId": 405625, + "transTypeName": "收单收入", + "transformAmount": 290, + "transformCrAmount": 290, + "transformDrAmount": 0, + "transfromBalanceAmount": 85688.37, + "trxBalance": 0, + "trxDate": "2025-02-03 00:00:00", + "trxFlag": "R", + "userMemo": "收单" + } + ], + "pageable": { + "hasNext": true, + "hasPre": false, + "isFirst": true, + "isLast": false, + "pageNow": 1, + "pageSize": 1, + "startPos": 0, + "totalCount": 3392, + "totalPageCount": 3392 + }, + "searchable": { + "appInput": 0, + "dayFromId": 0, + "dayToId": 0, + "endDateId": 0, + "enterpriseId": 0, + "groupTypeId": 0, + "logId": 19060, + "pageNow": 1, + "pageSize": 1, + "showDownPayment": 0, + "startDateId": 0, + "trxAmount": 0, + "trxTypeId": 0, + "uploadFromDateId": 0, + "uploadToDateId": 0, + "useForBsSearch": 0, + "useNameExactMatching": 0, + "withOrderBy": true + } + }, + "status": "200", + "successResponse": true +} +``` + +--- + +## 接口调用流程 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 1. 初始化调用 /account/common/getToken 接口创建项目 │ +│ ↓ │ +│ 2. 调用 /remoteUploadSplitFile 接口上传文件 │ +│ 或调用 /getJZFileOrZjrcuFile 拉取行内流水 │ +│ ↓ │ +│ 3. 调用 /getpendings 获取文件解析状态 │ +│ - parsing=true 时,间隔1s轮询 │ +│ - parsing=false 且 status=-5 表示解析成功 │ +│ ↓ │ +│ 4. 调用 /confirmStageUploadLogs 接口生成尽调报告 │ +│ ↓ │ +│ 5. 调用 /getallpending 检查尽调报告生成状态 │ +│ - pendingList 不为空时,间隔1s轮询 │ +│ - pendingList=[] 表示生成完成 │ +│ ↓ │ +│ 6. 调用 /getBankStatement 接口获取流水数据存储到兰溪本地 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 生产环境配置 + +| 配置项 | 值 | +|--------|-----| +| 生产IP | 64.202.32.176 | +| 生产X-Xencio-Client-Id | 通过接口获取 | + +### 获取生产环境 Client-Id + +``` +GET http://64.202.32.176/c4c3/watson/api/common/GenerateAccessKey?userLogin={流水分析平台登录柜员号} +``` + +--- + +## 附录:公共请求头 + +| 请求头 | 值 | 说明 | +|--------|-----|------| +| X-Xencio-Client-Id | c2017e8d105c435a96f86373635b6a09 | 测试环境固定值 | +| Content-Type | application/json | POST请求 | diff --git a/docs/plans/2026-02-28-database-migration-design.md b/docs/plans/2026-02-28-database-migration-design.md new file mode 100644 index 0000000..7ad1370 --- /dev/null +++ b/docs/plans/2026-02-28-database-migration-design.md @@ -0,0 +1,282 @@ +# 数据库迁移设计文档 + +## 概述 + +将 CCDI 纪检初核系统的开发环境数据库完整迁移到生产环境,包括所有表结构和数据的导出与导入。 + +## 需求分析 + +### 迁移目标 +- **源数据库**: 116.62.17.81:3306/ccdi +- **目标环境**: 全新的生产数据库(空库) +- **迁移范围**: 所有表的结构和数据 + +### 关键要求 +1. 表结构和数据分离导出(两个独立文件) +2. 只导出表,不包括视图、存储过程、触发器等 +3. 完整导出所有数据,不需要脱敏 +4. 确保字符集正确,避免乱码问题 +5. 使用 mysqldump 命令导出 +6. 提供自动化脚本简化操作 + +## 技术方案 + +### 导出工具 +使用 MySQL 官方工具 `mysqldump` 进行导出,优势: +- 标准化工具,兼容性最佳 +- 性能优秀,适合大数据库 +- 生成的 SQL 文件通用性强 + +### 字符集处理 +- **字符集**: utf8mb4(支持完整 Unicode,包括 emoji) +- **排序规则**: utf8mb4_general_ci +- **客户端字符集**: utf8mb4 + +关键措施: +1. mysqldump 命令添加 `--default-character-set=utf8mb4` 参数 +2. SQL 文件头部添加字符集声明语句 +3. 导入时指定字符集参数 +4. 导入后验证中文数据正确性 + +## 导出设计 + +### 文件组织 +``` +ccdi/ +├── export_database.sh # 自动化脚本 +├── db_config.conf.template # 配置模板 +├── db_config.conf # 实际配置(不纳入版本控制) +└── doc/ + └── database/ + └── backup/ + ├── ccdi_structure.sql # 表结构文件 + ├── ccdi_data.sql # 数据文件 + └── export_guide.md # 操作指南 +``` + +### 表结构导出命令 +```bash +mysqldump -h 116.62.17.81 -P 3306 -u root -p \ + --no-data \ + --skip-triggers \ + --skip-add-drop-table \ + --default-character-set=utf8mb4 \ + --single-transaction \ + ccdi > ccdi_structure.sql +``` + +**参数说明**: +- `--no-data`: 只导出表结构,不导出数据 +- `--skip-triggers`: 跳过触发器 +- `--skip-add-drop-table`: 不添加 DROP TABLE 语句(避免误删) +- `--default-character-set=utf8mb4`: 指定字符集 +- `--single-transaction`: InnoDB 表一致性导出,不锁表 + +### 数据导出命令 +```bash +mysqldump -h 116.62.17.81 -P 3306 -u root -p \ + --no-create-info \ + --skip-triggers \ + --default-character-set=utf8mb4 \ + --single-transaction \ + --complete-insert \ + --extended-insert \ + ccdi > ccdi_data.sql +``` + +**参数说明**: +- `--no-create-info`: 只导出数据,不导出表结构 +- `--complete-insert`: INSERT 语句包含列名 +- `--extended-insert`: 使用多行 INSERT,提高导入效率 + +### SQL 文件字符集声明 +```sql +SET NAMES utf8mb4; +SET CHARACTER SET utf8mb4; +SET GLOBAL character_set_client=utf8mb4; +SET GLOBAL character_set_connection=utf8mb4; +SET GLOBAL character_set_results=utf8mb4; +``` + +## 导入设计 + +### 导入顺序 +1. 先导入表结构:`ccdi_structure.sql` +2. 再导入数据:`ccdi_data.sql` + +### 导入命令 +```bash +# 导入表结构 +mysql -h 生产环境IP -P 3306 -u 用户名 -p \ + --default-character-set=utf8mb4 \ + 数据库名 < ccdi_structure.sql + +# 导入数据 +mysql -h 生产环境IP -P 3306 -u 用户名 -p \ + --default-character-set=utf8mb4 \ + 数据库名 < ccdi_data.sql +``` + +### 前置条件 +1. 目标数据库已创建(如:`CREATE DATABASE ccdi CHARACTER SET utf8mb4;`) +2. 目标用户有足够权限 +3. 磁盘空间充足 + +## 自动化脚本设计 + +### 脚本功能 +- **导出模式**: `./export_database.sh export` + - 检查 mysqldump 命令可用性 + - 创建备份目录 + - 执行结构导出和数据导出 + - 添加字符集声明到文件头部 + - 验证文件生成 + - 记录操作日志 + +- **导入模式**: `./export_database.sh import [production|test]` + - 读取配置文件获取目标环境信息 + - 检查目标数据库连接 + - 依次导入结构和数据文件 + - 验证导入结果 + - 记录操作日志 + +### 配置文件设计 +```bash +# 源数据库配置(开发环境) +SOURCE_DB_HOST=116.62.17.81 +SOURCE_DB_PORT=3306 +SOURCE_DB_USER=root +SOURCE_DB_PASS=Kfcx@1234 +SOURCE_DB_NAME=ccdi + +# 生产环境数据库配置 +PROD_DB_HOST=生产环境IP +PROD_DB_PORT=3306 +PROD_DB_USER=生产环境用户名 +PROD_DB_PASS=生产环境密码 +PROD_DB_NAME=ccdi + +# 测试环境数据库配置(可选) +TEST_DB_HOST=测试环境IP +TEST_DB_PORT=3306 +TEST_DB_USER=测试环境用户名 +TEST_DB_PASS=测试环境密码 +TEST_DB_NAME=ccdi + +# 导出文件配置 +BACKUP_DIR=doc/database/backup +STRUCTURE_FILE=ccdi_structure.sql +DATA_FILE=ccdi_data.sql +``` + +### 安全措施 +1. `db_config.conf` 添加到 `.gitignore` +2. 提供 `db_config.conf.template` 模板文件 +3. 首次运行时检测配置文件,提示用户填写 + +## 验证测试 + +### 导出验证 +1. 检查生成的 SQL 文件大小是否合理 +2. 检查文件头部是否包含字符集声明 +3. 随机抽取数据检查是否有乱码 +4. 统计表数量和数据行数 + +### 导入验证 +1. 在测试环境先进行导入测试 +2. 对比源数据库和目标数据库的表数量 +3. 抽查关键表的数据行数 +4. 查询包含中文的数据验证编码正确性 +5. 使用 `SHOW CREATE TABLE` 检查表字符集 + +### 验证命令 +```sql +-- 查看数据库字符集 +SHOW CREATE DATABASE ccdi; + +-- 查看表数量 +SELECT COUNT(*) FROM information_schema.tables +WHERE table_schema='ccdi'; + +-- 查看各表行数 +SELECT table_name, table_rows +FROM information_schema.tables +WHERE table_schema='ccdi' +ORDER BY table_rows DESC; + +-- 检查表字符集 +SHOW CREATE TABLE sys_user; +``` + +## 错误处理 + +### 常见问题 +1. **字符集乱码** + - 原因:未指定字符集参数 + - 解决:确保所有命令都添加 `--default-character-set=utf8mb4` + +2. **导入失败** + - 原因:外键约束冲突 + - 解决:导入前临时禁用外键检查 `SET FOREIGN_KEY_CHECKS=0;` + +3. **连接超时** + - 原因:数据库过大或网络慢 + - 解决:添加 `--max_allowed_packet=512M` 参数 + +4. **权限不足** + - 原因:用户权限不够 + - 解决:使用 root 用户或授予足够权限 + +## 操作流程 + +### 完整迁移流程 +1. 配置 `db_config.conf` 文件 +2. 执行导出:`./export_database.sh export` +3. 验证导出文件正确性 +4. 在测试环境验证导入:`./export_database.sh import test` +5. 验证测试环境数据完整性 +6. 在生产环境执行导入:`./export_database.sh import production` +7. 验证生产环境数据完整性 +8. 应用程序连接测试 + +### 回滚方案 +保留源数据库,如迁移失败可继续使用源数据库,重新执行迁移流程。 + +## 交付物 + +1. **自动化脚本**: `export_database.sh` +2. **配置模板**: `db_config.conf.template` +3. **表结构文件**: `doc/database/backup/ccdi_structure.sql` +4. **数据文件**: `doc/database/backup/ccdi_data.sql` +5. **操作指南**: `doc/database/backup/export_guide.md` +6. **设计文档**: `docs/plans/2026-02-28-database-migration-design.md` + +## 时间估算 + +- 脚本开发:30分钟 +- 导出执行:10-30分钟(取决于数据量) +- 测试环境导入验证:10-30分钟 +- 生产环境导入:10-30分钟 +- 验证测试:10分钟 + +**总计**: 约1.5-2小时 + +## 风险评估 + +| 风险项 | 等级 | 缓解措施 | +|--------|------|----------| +| 数据量过大导致超时 | 中 | 添加 max_allowed_packet 参数,分批导出 | +| 字符集乱码 | 高 | 严格遵循字符集规范,导入后验证 | +| 网络中断 | 低 | 本地保存SQL文件,可重复导入 | +| 生产环境数据冲突 | 无 | 全新空库,无冲突风险 | +| 权限问题 | 低 | 使用 root 用户或确保权限充足 | + +## 成功标准 + +1. ✅ 所有表结构成功导出,无遗漏 +2. ✅ 所有表数据成功导出,无丢失 +3. ✅ SQL 文件字符集正确,无乱码 +4. ✅ 测试环境导入成功,数据完整 +5. ✅ 生产环境导入成功,数据完整 +6. ✅ 中文数据正确显示,编码无误 +7. ✅ 应用程序可正常连接和操作数据库 diff --git a/docs/plans/2026-02-28-database-migration.md b/docs/plans/2026-02-28-database-migration.md new file mode 100644 index 0000000..cab425b --- /dev/null +++ b/docs/plans/2026-02-28-database-migration.md @@ -0,0 +1,1248 @@ +# 数据库迁移实施计划 + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** 创建自动化脚本完成 CCDI 数据库的完整导出和导入,包括表结构和数据,确保字符集正确无乱码 + +**Architecture:** 使用 mysqldump 命令导出数据库,分离表结构和数据为两个 SQL 文件,通过 Bash 脚本自动化管理导出和导入流程,配置文件管理多环境数据库连接信息 + +**Tech Stack:** Bash 脚本、mysqldump/mysql 命令行工具、UTF-8/utf8mb4 字符集 + +--- + +## Task 1: 创建配置文件模板和安全措施 + +**Files:** +- Create: `db_config.conf.template` +- Modify: `.gitignore` + +**Step 1: 创建配置文件模板** + +创建 `db_config.conf.template` 文件: + +```bash +# 数据库迁移配置文件模板 +# 使用方法:复制此文件为 db_config.conf 并填写实际值 + +# 源数据库配置(开发环境) +SOURCE_DB_HOST=116.62.17.81 +SOURCE_DB_PORT=3306 +SOURCE_DB_USER=root +SOURCE_DB_PASS=Kfcx@1234 +SOURCE_DB_NAME=ccdi + +# 生产环境数据库配置 +PROD_DB_HOST=your_production_host +PROD_DB_PORT=3306 +PROD_DB_USER=your_production_user +PROD_DB_PASS=your_production_password +PROD_DB_NAME=ccdi + +# 测试环境数据库配置(可选) +TEST_DB_HOST=your_test_host +TEST_DB_PORT=3306 +TEST_DB_USER=your_test_user +TEST_DB_PASS=your_test_password +TEST_DB_NAME=ccdi + +# 导出文件配置 +BACKUP_DIR=doc/database/backup +STRUCTURE_FILE=ccdi_structure.sql +DATA_FILE=ccdi_data.sql + +# mysqldump 参数配置 +CHARACTER_SET=utf8mb4 +MAX_ALLOWED_PACKET=512M +``` + +**Step 2: 更新 .gitignore 文件** + +在 `.gitignore` 文件末尾添加: + +``` +# 数据库配置文件(包含敏感信息) +db_config.conf +``` + +**Step 3: 提交配置模板** + +```bash +git add db_config.conf.template .gitignore +git commit -m "feat: 添加数据库迁移配置模板和安全措施" +``` + +--- + +## Task 2: 创建备份目录结构 + +**Files:** +- Create: `doc/database/backup/.gitkeep` + +**Step 1: 创建备份目录** + +```bash +mkdir -p doc/database/backup +``` + +**Step 2: 创建 .gitkeep 文件保持目录** + +```bash +touch doc/database/backup/.gitkeep +``` + +**Step 3: 提交目录结构** + +```bash +git add doc/database/backup/.gitkeep +git commit -m "feat: 创建数据库备份目录结构" +``` + +--- + +## Task 3: 创建自动化导出脚本(框架和导出功能) + +**Files:** +- Create: `export_database.sh` + +**Step 1: 创建脚本文件并添加基本框架** + +创建 `export_database.sh` 文件: + +```bash +#!/bin/bash + +# CCDI 数据库迁移自动化脚本 +# 功能:数据库导出和导入自动化 + +set -e # 遇到错误立即退出 + +# 颜色定义 +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" +} + +# 脚本目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONFIG_FILE="${SCRIPT_DIR}/db_config.conf" + +# 检查配置文件 +check_config() { + if [ ! -f "$CONFIG_FILE" ]; then + log_error "配置文件不存在: $CONFIG_FILE" + log_info "请先复制配置模板: cp db_config.conf.template db_config.conf" + log_info "然后编辑 db_config.conf 填写实际数据库连接信息" + exit 1 + fi + + # 加载配置文件 + source "$CONFIG_FILE" + log_info "配置文件加载成功" +} + +# 检查 mysqldump 命令 +check_mysqldump() { + if ! command -v mysqldump &> /dev/null; then + log_error "mysqldump 命令未找到" + log_info "请安装 MySQL 客户端工具" + exit 1 + fi + log_info "mysqldump 命令检查通过" +} + +# 创建备份目录 +create_backup_dir() { + if [ ! -d "$BACKUP_DIR" ]; then + mkdir -p "$BACKUP_DIR" + log_info "创建备份目录: $BACKUP_DIR" + fi +} + +# 导出表结构 +export_structure() { + log_info "开始导出表结构..." + + local output_file="${BACKUP_DIR}/${STRUCTURE_FILE}" + + # 创建临时文件 + local temp_file=$(mktemp) + + # 导出表结构到临时文件 + mysqldump -h "$SOURCE_DB_HOST" \ + -P "$SOURCE_DB_PORT" \ + -u "$SOURCE_DB_USER" \ + -p"$SOURCE_DB_PASS" \ + --no-data \ + --skip-triggers \ + --skip-add-drop-table \ + --default-character-set=$CHARACTER_SET \ + --single-transaction \ + --max_allowed_packet=$MAX_ALLOWED_PACKET \ + "$SOURCE_DB_NAME" > "$temp_file" 2>/dev/null + + if [ $? -eq 0 ]; then + # 添加字符集声明到文件头部 + { + echo "-- CCDI 数据库表结构" + echo "-- 导出时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo "-- 源数据库: ${SOURCE_DB_HOST}:${SOURCE_DB_PORT}/${SOURCE_DB_NAME}" + echo "-- 字符集: ${CHARACTER_SET}" + echo "" + echo "SET NAMES utf8mb4;" + echo "SET CHARACTER SET utf8mb4;" + echo "SET GLOBAL character_set_client=utf8mb4;" + echo "SET GLOBAL character_set_connection=utf8mb4;" + echo "SET GLOBAL character_set_results=utf8mb4;" + echo "" + cat "$temp_file" + } > "$output_file" + + rm -f "$temp_file" + log_info "表结构导出成功: $output_file" + log_info "文件大小: $(du -h "$output_file" | cut -f1)" + else + rm -f "$temp_file" + log_error "表结构导出失败" + exit 1 + fi +} + +# 导出数据 +export_data() { + log_info "开始导出数据..." + + local output_file="${BACKUP_DIR}/${DATA_FILE}" + + # 创建临时文件 + local temp_file=$(mktemp) + + # 导出数据到临时文件 + mysqldump -h "$SOURCE_DB_HOST" \ + -P "$SOURCE_DB_PORT" \ + -u "$SOURCE_DB_USER" \ + -p"$SOURCE_DB_PASS" \ + --no-create-info \ + --skip-triggers \ + --default-character-set=$CHARACTER_SET \ + --single-transaction \ + --complete-insert \ + --extended-insert \ + --max_allowed_packet=$MAX_ALLOWED_PACKET \ + "$SOURCE_DB_NAME" > "$temp_file" 2>/dev/null + + if [ $? -eq 0 ]; then + # 添加字符集声明到文件头部 + { + echo "-- CCDI 数据库数据" + echo "-- 导出时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo "-- 源数据库: ${SOURCE_DB_HOST}:${SOURCE_DB_PORT}/${SOURCE_DB_NAME}" + echo "-- 字符集: ${CHARACTER_SET}" + echo "" + echo "SET NAMES utf8mb4;" + echo "SET CHARACTER SET utf8mb4;" + echo "SET GLOBAL character_set_client=utf8mb4;" + echo "SET GLOBAL character_set_connection=utf8mb4;" + echo "SET GLOBAL character_set_results=utf8mb4;" + echo "SET FOREIGN_KEY_CHECKS=0;" + echo "" + cat "$temp_file" + echo "" + echo "SET FOREIGN_KEY_CHECKS=1;" + } > "$output_file" + + rm -f "$temp_file" + log_info "数据导出成功: $output_file" + log_info "文件大小: $(du -h "$output_file" | cut -f1)" + else + rm -f "$temp_file" + log_error "数据导出失败" + exit 1 + fi +} + +# 验证导出文件 +verify_export() { + log_info "验证导出文件..." + + local structure_file="${BACKUP_DIR}/${STRUCTURE_FILE}" + local data_file="${BACKUP_DIR}/${DATA_FILE}" + + # 检查文件是否存在 + if [ ! -f "$structure_file" ]; then + log_error "表结构文件不存在: $structure_file" + exit 1 + fi + + if [ ! -f "$data_file" ]; then + log_error "数据文件不存在: $data_file" + exit 1 + fi + + # 检查字符集声明 + if ! grep -q "SET NAMES utf8mb4" "$structure_file"; then + log_error "表结构文件缺少字符集声明" + exit 1 + fi + + if ! grep -q "SET NAMES utf8mb4" "$data_file"; then + log_error "数据文件缺少字符集声明" + exit 1 + fi + + log_info "导出文件验证通过" + log_info "表结构文件: $structure_file ($(du -h "$structure_file" | cut -f1))" + log_info "数据文件: $data_file ($(du -h "$data_file" | cut -f1))" +} + +# 导出数据库 +export_database() { + log_info "========== 开始导出数据库 ==========" + + check_config + check_mysqldump + create_backup_dir + export_structure + export_data + verify_export + + log_info "========== 数据库导出完成 ==========" +} + +# 使用帮助 +show_usage() { + echo "用法: $0 " + echo "" + echo "命令:" + echo " export 导出数据库" + echo " help 显示帮助信息" + echo "" + echo "示例:" + echo " $0 export # 导出数据库到 doc/database/backup/ 目录" +} + +# 主函数 +main() { + case "$1" in + export) + export_database + ;; + help|--help|-h) + show_usage + ;; + *) + log_error "未知命令: $1" + show_usage + exit 1 + ;; + esac +} + +# 执行主函数 +main "$@" +``` + +**Step 2: 设置脚本执行权限** + +```bash +chmod +x export_database.sh +``` + +**Step 3: 提交导出脚本** + +```bash +git add export_database.sh +git commit -m "feat: 创建数据库导出自动化脚本" +``` + +--- + +## Task 4: 执行数据库导出 + +**Files:** +- Generate: `doc/database/backup/ccdi_structure.sql` +- Generate: `doc/database/backup/ccdi_data.sql` + +**Step 1: 创建配置文件** + +```bash +cp db_config.conf.template db_config.conf +``` + +**Step 2: 验证配置文件内容** + +检查 `db_config.conf` 文件确保源数据库配置正确: +```bash +cat db_config.conf | grep "SOURCE_DB" +``` + +预期输出: +``` +SOURCE_DB_HOST=116.62.17.81 +SOURCE_DB_PORT=3306 +SOURCE_DB_USER=root +SOURCE_DB_PASS=Kfcx@1234 +SOURCE_DB_NAME=ccdi +``` + +**Step 3: 执行导出脚本** + +```bash +./export_database.sh export +``` + +预期输出: +``` +[INFO] 配置文件加载成功 +[INFO] mysqldump 命令检查通过 +[INFO] 开始导出表结构... +[INFO] 表结构导出成功: doc/database/backup/ccdi_structure.sql +[INFO] 文件大小: XXX +[INFO] 开始导出数据... +[INFO] 数据导出成功: doc/database/backup/ccdi_data.sql +[INFO] 文件大小: XXX +[INFO] 验证导出文件... +[INFO] 导出文件验证通过 +[INFO] ========== 数据库导出完成 ========== +``` + +**Step 4: 验证导出文件** + +检查表结构文件头部: +```bash +head -20 doc/database/backup/ccdi_structure.sql +``` + +预期输出应包含: +```sql +-- CCDI 数据库表结构 +-- 导出时间: 2026-02-28 XX:XX:XX +-- 源数据库: 116.62.17.81:3306/ccdi +-- 字符集: utf8mb4 + +SET NAMES utf8mb4; +SET CHARACTER SET utf8mb4; +... +``` + +检查数据文件头部: +```bash +head -20 doc/database/backup/ccdi_data.sql +``` + +预期输出应包含: +```sql +-- CCDI 数据库数据 +-- 导出时间: 2026-02-28 XX:XX:XX +-- 源数据库: 116.62.17.81:3306/ccdi +-- 字符集: utf8mb4 + +SET NAMES utf8mb4; +SET CHARACTER SET utf8mb4; +SET FOREIGN_KEY_CHECKS=0; +... +``` + +**Step 5: 检查文件大小** + +```bash +ls -lh doc/database/backup/ +``` + +预期输出应显示两个文件的大小。 + +--- + +## Task 5: 添加导入功能到脚本 + +**Files:** +- Modify: `export_database.sh` (添加导入函数) + +**Step 1: 在脚本中添加导入函数** + +在 `export_database.sh` 文件的 `verify_export()` 函数后添加以下函数: + +```bash + +# 导入表结构 +import_structure() { + local env_type=$1 + local db_host db_port db_user db_pass db_name + + case "$env_type" in + production|prod) + db_host="$PROD_DB_HOST" + db_port="$PROD_DB_PORT" + db_user="$PROD_DB_USER" + db_pass="$PROD_DB_PASS" + db_name="$PROD_DB_NAME" + ;; + test) + db_host="$TEST_DB_HOST" + db_port="$TEST_DB_PORT" + db_user="$TEST_DB_USER" + db_pass="$TEST_DB_PASS" + db_name="$TEST_DB_NAME" + ;; + *) + log_error "未知的环境类型: $env_type" + exit 1 + ;; + esac + + log_info "导入表结构到 ${env_type} 环境: ${db_host}:${db_port}/${db_name}" + + local structure_file="${BACKUP_DIR}/${STRUCTURE_FILE}" + + if [ ! -f "$structure_file" ]; then + log_error "表结构文件不存在: $structure_file" + log_info "请先执行导出: ./export_database.sh export" + exit 1 + fi + + # 导入表结构 + mysql -h "$db_host" \ + -P "$db_port" \ + -u "$db_user" \ + -p"$db_pass" \ + --default-character-set=$CHARACTER_SET \ + "$db_name" < "$structure_file" 2>/dev/null + + if [ $? -eq 0 ]; then + log_info "表结构导入成功" + else + log_error "表结构导入失败" + exit 1 + fi +} + +# 导入数据 +import_data() { + local env_type=$1 + local db_host db_port db_user db_pass db_name + + case "$env_type" in + production|prod) + db_host="$PROD_DB_HOST" + db_port="$PROD_DB_PORT" + db_user="$PROD_DB_USER" + db_pass="$PROD_DB_PASS" + db_name="$PROD_DB_NAME" + ;; + test) + db_host="$TEST_DB_HOST" + db_port="$TEST_DB_PORT" + db_user="$TEST_DB_USER" + db_pass="$TEST_DB_PASS" + db_name="$TEST_DB_NAME" + ;; + *) + log_error "未知的环境类型: $env_type" + exit 1 + ;; + esac + + log_info "导入数据到 ${env_type} 环境: ${db_host}:${db_port}/${db_name}" + + local data_file="${BACKUP_DIR}/${DATA_FILE}" + + if [ ! -f "$data_file" ]; then + log_error "数据文件不存在: $data_file" + log_info "请先执行导出: ./export_database.sh export" + exit 1 + fi + + # 导入数据 + mysql -h "$db_host" \ + -P "$db_port" \ + -u "$db_user" \ + -p"$db_pass" \ + --default-character-set=$CHARACTER_SET \ + "$db_name" < "$data_file" 2>/dev/null + + if [ $? -eq 0 ]; then + log_info "数据导入成功" + else + log_error "数据导入失败" + exit 1 + fi +} + +# 验证导入结果 +verify_import() { + local env_type=$1 + local db_host db_port db_user db_pass db_name + + case "$env_type" in + production|prod) + db_host="$PROD_DB_HOST" + db_port="$PROD_DB_PORT" + db_user="$PROD_DB_USER" + db_pass="$PROD_DB_PASS" + db_name="$PROD_DB_NAME" + ;; + test) + db_host="$TEST_DB_HOST" + db_port="$TEST_DB_PORT" + db_user="$TEST_DB_USER" + db_pass="$TEST_DB_PASS" + db_name="$TEST_DB_NAME" + ;; + *) + log_error "未知的环境类型: $env_type" + exit 1 + ;; + esac + + log_info "验证导入结果..." + + # 查询表数量 + local table_count=$(mysql -h "$db_host" \ + -P "$db_port" \ + -u "$db_user" \ + -p"$db_pass" \ + --default-character-set=$CHARACTER_SET \ + -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$db_name';" "$db_name" 2>/dev/null) + + log_info "目标数据库表数量: $table_count" + + # 查询关键表行数(示例:sys_user 表) + local user_count=$(mysql -h "$db_host" \ + -P "$db_port" \ + -u "$db_user" \ + -p"$db_pass" \ + --default-character-set=$CHARACTER_SET \ + -N -e "SELECT COUNT(*) FROM sys_user;" "$db_name" 2>/dev/null) + + log_info "sys_user 表数据行数: $user_count" + + # 检查数据库字符集 + local db_charset=$(mysql -h "$db_host" \ + -P "$db_port" \ + -u "$db_user" \ + -p"$db_pass" \ + --default-character-set=$CHARACTER_SET \ + -N -e "SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.schemata WHERE schema_name='$db_name';" 2>/dev/null) + + log_info "数据库字符集: $db_charset" +} + +# 导入数据库 +import_database() { + local env_type=$1 + + if [ -z "$env_type" ]; then + log_error "请指定目标环境: production 或 test" + log_info "用法: $0 import [production|test]" + exit 1 + fi + + log_info "========== 开始导入数据库到 ${env_type} 环境 ==========" + + check_config + import_structure "$env_type" + import_data "$env_type" + verify_import "$env_type" + + log_info "========== 数据库导入完成 ==========" +} +``` + +**Step 2: 更新 show_usage() 函数** + +替换 `show_usage()` 函数为: + +```bash +# 使用帮助 +show_usage() { + echo "用法: $0 [options]" + echo "" + echo "命令:" + echo " export 导出数据库" + echo " import 导入数据库到指定环境" + echo " help 显示帮助信息" + echo "" + echo "环境:" + echo " production, prod 生产环境" + echo " test 测试环境" + echo "" + echo "示例:" + echo " $0 export # 导出数据库到 doc/database/backup/ 目录" + echo " $0 import test # 导入数据库到测试环境" + echo " $0 import prod # 导入数据库到生产环境" +} +``` + +**Step 3: 更新 main() 函数** + +替换 `main()` 函数为: + +```bash +# 主函数 +main() { + case "$1" in + export) + export_database + ;; + import) + import_database "$2" + ;; + help|--help|-h) + show_usage + ;; + *) + log_error "未知命令: $1" + show_usage + exit 1 + ;; + esac +} +``` + +**Step 4: 提交导入功能** + +```bash +git add export_database.sh +git commit -m "feat: 添加数据库导入功能到自动化脚本" +``` + +--- + +## Task 6: 创建操作指南文档 + +**Files:** +- Create: `doc/database/backup/export_guide.md` + +**Step 1: 创建操作指南文档** + +创建 `doc/database/backup/export_guide.md` 文件: + +```markdown +# CCDI 数据库迁移操作指南 + +## 概述 + +本文档提供 CCDI 纪检初核系统数据库迁移的详细操作步骤,包括从开发环境导出数据库和导入到生产/测试环境。 + +## 前置条件 + +### 必需工具 +- MySQL 客户端工具(包含 mysqldump 和 mysql 命令) +- Bash shell 环境 +- 网络访问权限(能连接源数据库和目标数据库) + +### 检查 mysqldump 是否安装 +```bash +mysqldump --version +``` + +如果未安装,请根据操作系统安装 MySQL 客户端: +- **Windows**: 安装 MySQL Community Server +- **Linux (Ubuntu/Debian)**: `sudo apt-get install mysql-client` +- **Linux (CentOS/RHEL)**: `sudo yum install mysql` +- **macOS**: `brew install mysql-client` + +## 配置步骤 + +### 1. 创建配置文件 + +```bash +# 复制配置模板 +cp db_config.conf.template db_config.conf + +# 编辑配置文件 +nano db_config.conf # 或使用其他编辑器 +``` + +### 2. 填写配置信息 + +编辑 `db_config.conf` 文件,填写以下信息: + +**源数据库(开发环境):** +- `SOURCE_DB_HOST`: 开发环境数据库地址 +- `SOURCE_DB_PORT`: 数据库端口(默认 3306) +- `SOURCE_DB_USER`: 数据库用户名 +- `SOURCE_DB_PASS`: 数据库密码 +- `SOURCE_DB_NAME`: 数据库名称(ccdi) + +**生产环境数据库:** +- `PROD_DB_HOST`: 生产环境数据库地址 +- `PROD_DB_PORT`: 数据库端口 +- `PROD_DB_USER`: 生产环境数据库用户名 +- `PROD_DB_PASS`: 生产环境数据库密码 +- `PROD_DB_NAME`: 数据库名称(ccdi) + +**测试环境数据库(可选):** +- `TEST_DB_HOST`: 测试环境数据库地址 +- `TEST_DB_PORT`: 数据库端口 +- `TEST_DB_USER`: 测试环境数据库用户名 +- `TEST_DB_PASS`: 测试环境数据库密码 +- `TEST_DB_NAME`: 数据库名称(ccdi) + +### 3. 验证配置 + +检查配置文件格式: +```bash +cat db_config.conf | grep -E "^[A-Z]" +``` + +确保所有必需的配置项都已填写。 + +## 数据库导出 + +### 执行导出 + +```bash +./export_database.sh export +``` + +### 预期输出 + +``` +[INFO] 配置文件加载成功 +[INFO] mysqldump 命令检查通过 +[INFO] 开始导出表结构... +[INFO] 表结构导出成功: doc/database/backup/ccdi_structure.sql +[INFO] 文件大小: XXX +[INFO] 开始导出数据... +[INFO] 数据导出成功: doc/database/backup/ccdi_data.sql +[INFO] 文件大小: XXX +[INFO] 验证导出文件... +[INFO] 导出文件验证通过 +[INFO] ========== 数据库导出完成 ========== +``` + +### 验证导出文件 + +**1. 检查文件是否存在** +```bash +ls -lh doc/database/backup/ +``` + +应该看到: +- `ccdi_structure.sql` - 表结构文件 +- `ccdi_data.sql` - 数据文件 + +**2. 检查字符集声明** +```bash +head -20 doc/database/backup/ccdi_structure.sql +``` + +应该包含: +```sql +SET NAMES utf8mb4; +SET CHARACTER SET utf8mb4; +``` + +**3. 检查文件内容** +```bash +# 查看表结构 +grep "CREATE TABLE" doc/database/backup/ccdi_structure.sql | wc -l + +# 查看数据量(INSERT 语句数量) +grep "INSERT" doc/database/backup/ccdi_data.sql | wc -l +``` + +## 数据库导入 + +### 准备工作 + +**1. 确认目标数据库已创建** + +连接到目标数据库服务器: +```bash +mysql -h 目标IP -P 3306 -u 用户名 -p +``` + +创建数据库(如果不存在): +```sql +CREATE DATABASE ccdi CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; +``` + +**2. 确认用户权限** + +目标数据库用户需要以下权限: +- CREATE、ALTER、DROP(创建和修改表) +- INSERT、UPDATE、DELETE(数据操作) +- INDEX(创建索引) +- REFERENCES(外键约束) + +### 导入到测试环境 + +```bash +./export_database.sh import test +``` + +### 导入到生产环境 + +```bash +./export_database.sh import production +``` + +或简写: +```bash +./export_database.sh import prod +``` + +### 预期输出 + +``` +[INFO] ========== 开始导入数据库到 test 环境 ========== +[INFO] 导入表结构到 test 环境: XXX:3306/ccdi +[INFO] 表结构导入成功 +[INFO] 导入数据到 test 环境: XXX:3306/ccdi +[INFO] 数据导入成功 +[INFO] 验证导入结果... +[INFO] 目标数据库表数量: XX +[INFO] sys_user 表数据行数: XX +[INFO] 数据库字符集: utf8mb4 +[INFO] ========== 数据库导入完成 ========== +``` + +## 导入后验证 + +### 1. 验证表数量 + +连接到目标数据库: +```bash +mysql -h 目标IP -P 3306 -u 用户名 -p ccdi +``` + +查询表数量: +```sql +SELECT COUNT(*) FROM information_schema.tables +WHERE table_schema='ccdi'; +``` + +对比源数据库和目标数据库的表数量是否一致。 + +### 2. 验证数据行数 + +查询各表数据行数: +```sql +SELECT table_name, table_rows +FROM information_schema.tables +WHERE table_schema='ccdi' +ORDER BY table_rows DESC +LIMIT 20; +``` + +对比源数据库和目标数据库的关键表行数。 + +### 3. 验证字符集 + +检查数据库字符集: +```sql +SHOW CREATE DATABASE ccdi; +``` + +应该显示:`DEFAULT CHARACTER SET utf8mb4` + +检查表字符集: +```sql +SHOW CREATE TABLE sys_user; +``` + +应该显示:`ENGINE=InnoDB DEFAULT CHARSET=utf8mb4` + +### 4. 验证中文数据 + +查询包含中文的数据: +```sql +-- 查询用户表 +SELECT user_name, nick_name FROM sys_user LIMIT 10; + +-- 查询字典数据 +SELECT dict_label, dict_value FROM sys_dict_data LIMIT 10; +``` + +确保中文字符显示正常,无乱码。 + +### 5. 应用程序连接测试 + +修改应用程序配置文件连接到目标数据库,启动应用程序进行功能测试。 + +## 完整迁移流程示例 + +### 场景:从开发环境迁移到生产环境 + +**1. 配置数据库连接** +```bash +cp db_config.conf.template db_config.conf +nano db_config.conf +# 填写开发环境和生产环境数据库信息 +``` + +**2. 导出数据库** +```bash +./export_database.sh export +``` + +**3. 验证导出文件** +```bash +ls -lh doc/database/backup/ +head -20 doc/database/backup/ccdi_structure.sql +``` + +**4. 先在测试环境验证** +```bash +./export_database.sh import test +``` + +**5. 验证测试环境** +- 连接测试数据库验证数据 +- 应用程序连接测试环境进行功能测试 + +**6. 导入到生产环境** +```bash +./export_database.sh import production +``` + +**7. 验证生产环境** +- 连接生产数据库验证数据 +- 应用程序连接生产环境进行功能测试 + +**8. 完成迁移** + +## 常见问题 + +### 1. mysqldump: command not found + +**原因**: MySQL 客户端未安装或未添加到 PATH + +**解决**: +- 安装 MySQL 客户端工具 +- 或使用完整路径:`/usr/bin/mysqldump` + +### 2. 配置文件不存在 + +**错误信息**: `配置文件不存在: db_config.conf` + +**解决**: +```bash +cp db_config.conf.template db_config.conf +# 编辑 db_config.conf 填写实际配置 +``` + +### 3. 连接数据库失败 + +**可能原因**: +- 数据库地址、端口、用户名或密码错误 +- 防火墙阻止连接 +- 数据库服务未启动 + +**解决**: +- 检查配置文件中的连接信息 +- 使用 mysql 命令手动测试连接 +- 检查防火墙规则 + +### 4. 导入时字符集乱码 + +**原因**: 未正确指定字符集 + +**解决**: +- 确保导出文件包含字符集声明 +- 导入命令添加 `--default-character-set=utf8mb4` 参数 +- 脚本已自动处理,如仍有问题请检查数据库默认字符集 + +### 5. 外键约束失败 + +**错误信息**: `ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails` + +**解决**: +- 脚本已自动添加 `SET FOREIGN_KEY_CHECKS=0;` 和 `SET FOREIGN_KEY_CHECKS=1;` +- 如仍有问题,请检查数据完整性 + +### 6. 数据包过大 + +**错误信息**: `ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes` + +**解决**: +- 配置文件中的 `MAX_ALLOWED_PACKET=512M` 已处理此问题 +- 如数据量特别大,可增大此值 + +### 7. 权限不足 + +**错误信息**: `ERROR 1044 (42000): Access denied for user` + +**解决**: +- 使用具有足够权限的用户(如 root) +- 或授予用户必要权限 + +## 回滚方案 + +如果迁移失败或出现问题: + +1. **保留源数据库**: 不要删除开发环境数据库 +2. **重新迁移**: 修复问题后重新执行迁移流程 +3. **从备份恢复**: 如生产环境有备份,可从备份恢复 + +## 注意事项 + +1. **安全性**: + - `db_config.conf` 包含敏感信息,已添加到 `.gitignore` + - 不要将配置文件提交到版本控制系统 + - 迁移完成后建议删除配置文件中的密码 + +2. **性能**: + - 大数据库导出/导入可能需要较长时间 + - 建议在低峰期执行迁移 + - 确保有足够的磁盘空间 + +3. **数据一致性**: + - 导出期间源数据库应避免写入操作 + - 或使用 `--single-transaction` 参数(已包含) + +4. **字符集**: + - 确保所有步骤都使用 utf8mb4 字符集 + - 验证阶段重点检查中文数据 + +## 技术支持 + +如遇到问题: +1. 检查本文档的常见问题部分 +2. 查看脚本执行的错误信息 +3. 检查数据库连接和权限 +4. 查看数据库日志 + +## 相关文件 + +- 自动化脚本: `export_database.sh` +- 配置模板: `db_config.conf.template` +- 实际配置: `db_config.conf` +- 表结构文件: `doc/database/backup/ccdi_structure.sql` +- 数据文件: `doc/database/backup/ccdi_data.sql` +- 设计文档: `docs/plans/2026-02-28-database-migration-design.md` +``` + +**Step 2: 提交操作指南** + +```bash +git add doc/database/backup/export_guide.md +git commit -m "docs: 添加数据库迁移操作指南" +``` + +--- + +## Task 7: 完整验证流程 + +**Files:** +- 无文件修改,仅验证操作 + +**Step 1: 验证脚本帮助信息** + +```bash +./export_database.sh help +``` + +预期输出: +``` +用法: ./export_database.sh [options] + +命令: + export 导出数据库 + import 导入数据库到指定环境 + help 显示帮助信息 + +环境: + production, prod 生产环境 + test 测试环境 + +示例: + ./export_database.sh export # 导出数据库到 doc/database/backup/ 目录 + ./export_database.sh import test # 导入数据库到测试环境 + ./export_database.sh import prod # 导入数据库到生产环境 +``` + +**Step 2: 验证配置文件** + +```bash +cat db_config.conf +``` + +确保包含所有必需配置项。 + +**Step 3: 验证导出文件** + +```bash +# 检查文件存在 +ls -lh doc/database/backup/ccdi_*.sql + +# 检查文件内容 +head -30 doc/database/backup/ccdi_structure.sql +head -30 doc/database/backup/ccdi_data.sql + +# 统计表数量 +grep "CREATE TABLE" doc/database/backup/ccdi_structure.sql | wc -l + +# 统计 INSERT 语句 +grep "INSERT INTO" doc/database/backup/ccdi_data.sql | wc -l +``` + +**Step 4: 创建验证报告** + +创建临时验证报告: +```bash +cat > /tmp/migration_verify.txt << 'EOF' +数据库迁移验证报告 +================== +导出时间: $(date) + +文件信息: +- 表结构文件: $(ls -lh doc/database/backup/ccdi_structure.sql) +- 数据文件: $(ls -lh doc/database/backup/ccdi_data.sql) + +表数量: $(grep "CREATE TABLE" doc/database/backup/ccdi_structure.sql | wc -l) + +字符集检查: +$(grep "SET NAMES utf8mb4" doc/database/backup/ccdi_structure.sql) + +验证结果: 通过 +EOF +cat /tmp/migration_verify.txt +``` + +--- + +## 成功标准 + +完成后,应满足以下所有条件: + +1. ✅ 配置文件模板 `db_config.conf.template` 已创建 +2. ✅ `db_config.conf` 已添加到 `.gitignore` +3. ✅ 自动化脚本 `export_database.sh` 可执行 +4. ✅ 执行 `./export_database.sh export` 成功生成两个 SQL 文件 +5. ✅ `ccdi_structure.sql` 包含所有表结构和字符集声明 +6. ✅ `ccdi_data.sql` 包含所有数据和字符集声明 +7. ✅ SQL 文件无乱码,字符集正确 +8. ✅ 操作指南文档清晰完整 +9. ✅ 所有代码已提交到 Git + +## 执行建议 + +**建议执行顺序**: +1. 在测试环境完整执行一遍流程 +2. 验证测试环境导入的数据完整性 +3. 确认无问题后,在生产环境执行 +4. 验证生产环境数据完整性 +5. 应用程序连接测试 + +**时间估算**: +- Task 1-3: 15分钟(脚本开发) +- Task 4: 20-30分钟(导出执行,取决于数据量) +- Task 5: 10分钟(添加导入功能) +- Task 6-7: 15分钟(文档和验证) + +**总计**: 约 60-70 分钟 diff --git a/export_database.sh b/export_database.sh deleted file mode 100644 index c22a5e6..0000000 --- a/export_database.sh +++ /dev/null @@ -1,269 +0,0 @@ -#!/bin/bash - -# CCDI 数据库导出脚本 -# 功能:从开发环境导出数据库到 backup 文件夹 - -set -e # 遇到错误立即退出 - -# ===================================================== -# 数据库配置(请根据实际情况修改) -# ===================================================== - -# 源数据库配置(开发环境) -DB_HOST="116.62.17.81" -DB_PORT="3306" -DB_USER="root" -DB_PASS="Kfcx@1234" -DB_NAME="ccdi" - -# 导出文件配置 -BACKUP_DIR="doc/database/backup" -STRUCTURE_FILE="ccdi_structure.sql" -DATA_FILE="ccdi_data.sql" - -# mysqldump 参数配置 -CHARACTER_SET="utf8mb4" -MAX_ALLOWED_PACKET="512M" - -# ===================================================== -# 脚本逻辑(无需修改) -# ===================================================== - -# 颜色定义 -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" -} - -# 脚本目录 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# 检查 mysqldump 命令 -check_mysqldump() { - if ! command -v mysqldump &> /dev/null; then - log_error "mysqldump 命令未找到" - log_info "请安装 MySQL 客户端工具" - exit 1 - fi - log_info "mysqldump 命令检查通过" -} - -# 创建备份目录 -create_backup_dir() { - if [ ! -d "$BACKUP_DIR" ]; then - mkdir -p "$BACKUP_DIR" - log_info "创建备份目录: $BACKUP_DIR" - fi -} - -# 导出表结构 -export_structure() { - log_info "开始导出表结构..." - - local output_file="${BACKUP_DIR}/${STRUCTURE_FILE}" - - # 创建临时文件 - local temp_file=$(mktemp) - - # 导出表结构到临时文件 - mysqldump -h "$DB_HOST" \ - -P "$DB_PORT" \ - -u "$DB_USER" \ - -p"$DB_PASS" \ - --no-data \ - --skip-triggers \ - --skip-add-drop-table \ - --default-character-set=$CHARACTER_SET \ - --single-transaction \ - --max_allowed_packet=$MAX_ALLOWED_PACKET \ - "$DB_NAME" > "$temp_file" 2>/dev/null - - if [ $? -eq 0 ]; then - # 添加字符集声明到文件头部 - { - echo "-- CCDI 数据库表结构" - echo "-- 导出时间: $(date '+%Y-%m-%d %H:%M:%S')" - echo "-- 源数据库: ${DB_HOST}:${DB_PORT}/${DB_NAME}" - echo "-- 字符集: ${CHARACTER_SET}" - echo "" - echo "SET NAMES utf8mb4;" - echo "SET CHARACTER SET utf8mb4;" - echo "SET GLOBAL character_set_client=utf8mb4;" - echo "SET GLOBAL character_set_connection=utf8mb4;" - echo "SET GLOBAL character_set_results=utf8mb4;" - echo "" - cat "$temp_file" - } > "$output_file" - - rm -f "$temp_file" - log_info "表结构导出成功: $output_file" - log_info "文件大小: $(du -h "$output_file" | cut -f1)" - else - rm -f "$temp_file" - log_error "表结构导出失败" - exit 1 - fi -} - -# 导出数据 -export_data() { - log_info "开始导出数据..." - - local output_file="${BACKUP_DIR}/${DATA_FILE}" - - # 创建临时文件 - local temp_file=$(mktemp) - - # 导出数据到临时文件 - mysqldump -h "$DB_HOST" \ - -P "$DB_PORT" \ - -u "$DB_USER" \ - -p"$DB_PASS" \ - --no-create-info \ - --skip-triggers \ - --default-character-set=$CHARACTER_SET \ - --single-transaction \ - --complete-insert \ - --extended-insert \ - --max_allowed_packet=$MAX_ALLOWED_PACKET \ - "$DB_NAME" > "$temp_file" 2>/dev/null - - if [ $? -eq 0 ]; then - # 添加字符集声明到文件头部 - { - echo "-- CCDI 数据库数据" - echo "-- 导出时间: $(date '+%Y-%m-%d %H:%M:%S')" - echo "-- 源数据库: ${DB_HOST}:${DB_PORT}/${DB_NAME}" - echo "-- 字符集: ${CHARACTER_SET}" - echo "" - echo "SET NAMES utf8mb4;" - echo "SET CHARACTER SET utf8mb4;" - echo "SET GLOBAL character_set_client=utf8mb4;" - echo "SET GLOBAL character_set_connection=utf8mb4;" - echo "SET GLOBAL character_set_results=utf8mb4;" - echo "SET FOREIGN_KEY_CHECKS=0;" - echo "" - cat "$temp_file" - echo "" - echo "SET FOREIGN_KEY_CHECKS=1;" - } > "$output_file" - - rm -f "$temp_file" - log_info "数据导出成功: $output_file" - log_info "文件大小: $(du -h "$output_file" | cut -f1)" - else - rm -f "$temp_file" - log_error "数据导出失败" - exit 1 - fi -} - -# 验证导出文件 -verify_export() { - log_info "验证导出文件..." - - local structure_file="${BACKUP_DIR}/${STRUCTURE_FILE}" - local data_file="${BACKUP_DIR}/${DATA_FILE}" - - # 检查文件是否存在 - if [ ! -f "$structure_file" ]; then - log_error "表结构文件不存在: $structure_file" - exit 1 - fi - - if [ ! -f "$data_file" ]; then - log_error "数据文件不存在: $data_file" - exit 1 - fi - - # 检查字符集声明 - if ! grep -q "SET NAMES utf8mb4" "$structure_file"; then - log_error "表结构文件缺少字符集声明" - exit 1 - fi - - if ! grep -q "SET NAMES utf8mb4" "$data_file"; then - log_error "数据文件缺少字符集声明" - exit 1 - fi - - log_info "导出文件验证通过" - log_info "表结构文件: $structure_file ($(du -h "$structure_file" | cut -f1))" - log_info "数据文件: $data_file ($(du -h "$data_file" | cut -f1))" -} - -# 导出数据库 -export_database() { - log_info "========== 开始导出数据库 ==========" - - check_mysqldump - create_backup_dir - export_structure - export_data - verify_export - - log_info "========== 数据库导出完成 ==========" - log_info "使用 ./import_database.sh 导入到目标环境" -} - -# 使用帮助 -show_usage() { - echo "用法: $0 [command]" - echo "" - echo "命令:" - echo " export 导出数据库(默认命令)" - echo " help 显示帮助信息" - echo "" - echo "说明:" - echo " 此脚本从开发环境导出数据库到 doc/database/backup/ 文件夹" - echo " 生成文件:" - echo " - ccdi_structure.sql (表结构)" - echo " - ccdi_data.sql (数据)" - echo "" - echo "配置:" - echo " 数据库配置已内置在脚本顶部,请根据实际情况修改" - echo " 配置项: DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME" - echo "" - echo "前置条件:" - echo " 已安装 MySQL 客户端工具(mysqldump)" - echo "" - echo "示例:" - echo " $0 # 导出数据库(默认)" - echo " $0 export # 导出数据库" - echo "" - echo "后续操作:" - echo " 使用 ./import_database.sh 导入到目标环境" -} - -# 主函数 -main() { - case "$1" in - export|"") - export_database - ;; - help|--help|-h) - show_usage - ;; - *) - log_error "未知命令: $1" - show_usage - exit 1 - ;; - esac -} - -# 执行主函数 -main "$@" diff --git a/import_database.sh b/import_database.sh deleted file mode 100644 index 10ea609..0000000 --- a/import_database.sh +++ /dev/null @@ -1,300 +0,0 @@ -#!/bin/bash - -# CCDI 数据库导入脚本 -# 功能:从 backup 文件夹导入数据库到目标环境 - -set -e # 遇到错误立即退出 - -# ===================================================== -# 数据库配置(请根据实际情况修改) -# ===================================================== - -# 开发环境数据库配置 -DEV_DB_HOST="116.62.17.81" -DEV_DB_PORT="3306" -DEV_DB_USER="root" -DEV_DB_PASS="Kfcx@1234" -DEV_DB_NAME="ccdi" - -# 测试环境数据库配置 -TEST_DB_HOST="your_test_host" -TEST_DB_PORT="3306" -TEST_DB_USER="your_test_user" -TEST_DB_PASS="your_test_password" -TEST_DB_NAME="ccdi" - -# 生产环境数据库配置 -PROD_DB_HOST="your_production_host" -PROD_DB_PORT="3306" -PROD_DB_USER="your_production_user" -PROD_DB_PASS="your_production_password" -PROD_DB_NAME="ccdi" - -# 备份文件配置 -BACKUP_DIR="doc/database/backup" -STRUCTURE_FILE="ccdi_structure.sql" -DATA_FILE="ccdi_data.sql" - -# ===================================================== -# 脚本逻辑(无需修改) -# ===================================================== - -# 颜色定义 -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" -} - -# 脚本目录 -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# 检查 mysql 命令 -check_mysql() { - if ! command -v mysql &> /dev/null; then - log_error "mysql 命令未找到" - log_info "请安装 MySQL 客户端工具" - exit 1 - fi - log_info "mysql 命令检查通过" -} - -# 检查备份文件 -check_backup_files() { - log_info "检查备份文件..." - - local structure_file="${BACKUP_DIR}/${STRUCTURE_FILE}" - local data_file="${BACKUP_DIR}/${DATA_FILE}" - - if [ ! -f "$structure_file" ]; then - log_error "表结构文件不存在: $structure_file" - log_info "请先执行导出: ./export_database.sh" - exit 1 - fi - - if [ ! -f "$data_file" ]; then - log_error "数据文件不存在: $data_file" - log_info "请先执行导出: ./export_database.sh" - exit 1 - fi - - log_info "备份文件检查通过" - log_info "表结构文件: $structure_file ($(du -h "$structure_file" | cut -f1))" - log_info "数据文件: $data_file ($(du -h "$data_file" | cut -f1))" -} - -# 获取环境数据库配置 -get_db_config() { - local env_type=$1 - - case "$env_type" in - dev|development) - DB_HOST="$DEV_DB_HOST" - DB_PORT="$DEV_DB_PORT" - DB_USER="$DEV_DB_USER" - DB_PASS="$DEV_DB_PASS" - DB_NAME="$DEV_DB_NAME" - ;; - test) - DB_HOST="$TEST_DB_HOST" - DB_PORT="$TEST_DB_PORT" - DB_USER="$TEST_DB_USER" - DB_PASS="$TEST_DB_PASS" - DB_NAME="$TEST_DB_NAME" - ;; - production|prod) - DB_HOST="$PROD_DB_HOST" - DB_PORT="$PROD_DB_PORT" - DB_USER="$PROD_DB_USER" - DB_PASS="$PROD_DB_PASS" - DB_NAME="$PROD_DB_NAME" - ;; - *) - log_error "未知的环境类型: $env_type" - exit 1 - ;; - esac -} - -# 导入表结构 -import_structure() { - local env_type=$1 - - get_db_config "$env_type" - - log_info "导入表结构到 ${env_type} 环境: ${DB_HOST}:${DB_PORT}/${DB_NAME}" - - local structure_file="${BACKUP_DIR}/${STRUCTURE_FILE}" - - # 导入表结构 - mysql -h "$DB_HOST" \ - -P "$DB_PORT" \ - -u "$DB_USER" \ - -p"$DB_PASS" \ - --default-character-set=utf8mb4 \ - "$DB_NAME" < "$structure_file" 2>/dev/null - - if [ $? -eq 0 ]; then - log_info "表结构导入成功" - else - log_error "表结构导入失败" - exit 1 - fi -} - -# 导入数据 -import_data() { - local env_type=$1 - - get_db_config "$env_type" - - log_info "导入数据到 ${env_type} 环境: ${DB_HOST}:${DB_PORT}/${DB_NAME}" - - local data_file="${BACKUP_DIR}/${DATA_FILE}" - - # 导入数据 - mysql -h "$DB_HOST" \ - -P "$DB_PORT" \ - -u "$DB_USER" \ - -p"$DB_PASS" \ - --default-character-set=utf8mb4 \ - "$DB_NAME" < "$data_file" 2>/dev/null - - if [ $? -eq 0 ]; then - log_info "数据导入成功" - else - log_error "数据导入失败" - exit 1 - fi -} - -# 验证导入结果 -verify_import() { - local env_type=$1 - - get_db_config "$env_type" - - log_info "验证导入结果..." - - # 查询表数量 - local table_count=$(mysql -h "$DB_HOST" \ - -P "$DB_PORT" \ - -u "$DB_USER" \ - -p"$DB_PASS" \ - --default-character-set=utf8mb4 \ - -N -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DB_NAME';" "$DB_NAME" 2>/dev/null) - - log_info "目标数据库表数量: $table_count" - - # 查询关键表行数(示例:sys_user 表) - local user_count=$(mysql -h "$DB_HOST" \ - -P "$DB_PORT" \ - -u "$DB_USER" \ - -p"$DB_PASS" \ - --default-character-set=utf8mb4 \ - -N -e "SELECT COUNT(*) FROM sys_user;" "$DB_NAME" 2>/dev/null) - - log_info "sys_user 表数据行数: $user_count" - - # 检查数据库字符集 - local db_charset=$(mysql -h "$DB_HOST" \ - -P "$DB_PORT" \ - -u "$DB_USER" \ - -p"$DB_PASS" \ - --default-character-set=utf8mb4 \ - -N -e "SELECT DEFAULT_CHARACTER_SET_NAME FROM information_schema.schemata WHERE schema_name='$DB_NAME';" 2>/dev/null) - - log_info "数据库字符集: $db_charset" -} - -# 导入数据库 -import_database() { - local env_type=$1 - - if [ -z "$env_type" ]; then - log_error "请指定目标环境: dev, test 或 production" - log_info "用法: $0 " - exit 1 - fi - - log_info "========== 开始导入数据库到 ${env_type} 环境 ==========" - - check_mysql - check_backup_files - import_structure "$env_type" - import_data "$env_type" - verify_import "$env_type" - - log_info "========== 数据库导入完成 ==========" -} - -# 使用帮助 -show_usage() { - echo "用法: $0 " - echo "" - echo "环境:" - echo " dev, development 开发环境" - echo " test 测试环境" - echo " production, prod 生产环境" - echo "" - echo "说明:" - echo " 此脚本从 doc/database/backup/ 文件夹读取备份文件并导入到指定环境" - echo " 备份文件:" - echo " - ccdi_structure.sql (表结构)" - echo " - ccdi_data.sql (数据)" - echo "" - echo "配置:" - echo " 数据库配置已内置在脚本顶部,请根据实际情况修改" - echo " 配置项: DEV_DB_*, TEST_DB_*, PROD_DB_*" - echo "" - echo "前置条件:" - echo " 1. 已执行 ./export_database.sh 导出数据库" - echo " 2. 已在脚本顶部配置目标环境数据库信息" - echo "" - echo "示例:" - echo " $0 test # 导入到测试环境" - echo " $0 prod # 导入到生产环境" - echo " $0 dev # 导入到开发环境" -} - -# 主函数 -main() { - case "$1" in - dev|development) - import_database "dev" - ;; - test) - import_database "test" - ;; - production|prod) - import_database "production" - ;; - help|--help|-h) - show_usage - ;; - *) - if [ -z "$1" ]; then - log_error "缺少环境参数" - else - log_error "未知的环境: $1" - fi - show_usage - exit 1 - ;; - esac -} - -# 执行主函数 -main "$@"