From 45e409636605acf07bc9d5351f4a3c74b8265b7f Mon Sep 17 00:00:00 2001
From: wkc <978997012@qq.com>
Date: Wed, 11 Feb 2026 16:59:42 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=89=A7=E8=A1=8C=E4=BF=A1=E8=B4=B7?=
=?UTF-8?q?=E5=AE=A2=E6=88=B7=E5=AE=B6=E5=BA=AD=E5=85=B3=E7=B3=BB=E8=8F=9C?=
=?UTF-8?q?=E5=8D=95=E6=9D=83=E9=99=90SQL?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 插入主菜单(信息维护下第5位)
- 插入6个按钮权限(查询/新增/修改/删除/导出/导入)
- 菜单ID: 2068
- 权限前缀: ccdi:custFmyRelation
---
...cust-fmy-relation-import-alignment-test.md | 423 ++++++++++++++++++
.../test-cust-fmy-relation-import.bat | 107 +++++
...2-11-cust-fmy-relation-import-alignment.md | 373 +++++++++++++++
3 files changed, 903 insertions(+)
create mode 100644 doc/test-reports/2026-02-11-cust-fmy-relation-import-alignment-test.md
create mode 100644 doc/test-scripts/test-cust-fmy-relation-import.bat
create mode 100644 docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md
diff --git a/doc/test-reports/2026-02-11-cust-fmy-relation-import-alignment-test.md b/doc/test-reports/2026-02-11-cust-fmy-relation-import-alignment-test.md
new file mode 100644
index 0000000..c0f323f
--- /dev/null
+++ b/doc/test-reports/2026-02-11-cust-fmy-relation-import-alignment-test.md
@@ -0,0 +1,423 @@
+# 信贷客户家庭关系导入功能对齐测试报告
+
+## 修改概述
+
+本次修改将**信贷客户家庭关系**功能的导入实现完全对齐到**员工亲属关系**的成熟模式,提升了代码质量、性能和用户体验。
+
+**修改日期**: 2026-02-11
+**参考模板**: `CcdiStaffEnterpriseRelationImportServiceImpl`
+**修改对象**: `CcdiCustFmyRelationImportServiceImpl`
+
+---
+
+## 修改文件清单
+
+### 1. Mapper 层
+**文件**: `CcdiCustFmyRelationMapper.java`
+- ✅ 新增 `batchExistsByCombinations` 方法接口
+- ✅ 支持批量查询已存在的关系组合
+
+**文件**: `CcdiCustFmyRelationMapper.xml`
+- ✅ 实现 `batchExistsByCombinations` SQL
+- ✅ 优化:从 N 次查询减少到 1 次查询
+
+```xml
+
+```
+
+### 2. Service 层
+
+**文件**: `CcdiCustFmyRelationImportServiceImpl.java`
+- ✅ 完全重构,参考员工亲属关系实现
+- ✅ 引入 `ImportLogUtils` 统一日志记录
+- ✅ 实现 `getExistingCombinations` 批量查询
+- ✅ 添加 Excel 内部重复检查
+- ✅ 优化 Redis 状态管理(Hash 结构)
+- ✅ 实现分批插入(每批500条)
+- ✅ 添加 `getImportStatus` 方法
+- ✅ 优化失败记录存储(JSON 序列化,7天过期)
+
+**文件**: `CcdiCustFmyRelationServiceImpl.java`
+- ✅ 更新 `importRelations` 方法,传递 userName 参数
+- ✅ 初始化 Redis 状态为 Hash 结构
+- ✅ 使用 `EasyExcelUtil` 进行导出和模板下载
+- ✅ 添加数据量校验
+
+### 3. Controller 层
+
+**文件**: `CcdiCustFmyRelationController.java`
+- ✅ 导入接口返回 `ImportResultVO` 对象
+- ✅ 状态查询接口返回 `ImportStatusVO` 对象
+- ✅ 失败记录接口支持分页
+- ✅ 使用 `EasyExcelUtil` 工具类
+
+### 4. VO 类
+- ✅ 复用 `ImportStatusVO.java`
+- ✅ 复用 `ImportResultVO.java`
+- ✅ 复用 `CustFmyRelationImportFailureVO.java`
+
+### 5. Excel 实体
+**文件**: `CcdiCustFmyRelationExcel.java`
+- ✅ 已包含完整的 `@DictDropdown` 注解
+ - `ccdi_relation_type` (关系类型)
+ - `ccdi_indiv_gender` (性别)
+ - `ccdi_certificate_type` (证件类型)
+
+---
+
+## 核心改进点
+
+### 1. 性能优化
+| 项目 | 优化前 | 优化后 | 提升 |
+|------|--------|--------|------|
+| 唯一性检查 | N 次数据库查询 | 1 次批量查询 | 约 90% |
+| 批量插入 | 无分批控制 | 每批 500 条 | 更稳定 |
+| 导入1000条 | 预计 30-50秒 | 预计 10-15秒 | 约 60% |
+
+### 2. Redis 状态管理升级
+
+**优化前**:
+```
+Key: import:custFmyRelation:{taskId}
+Value: "COMPLETED:10:5"
+TTL: 1 小时
+```
+
+**优化后**:
+```
+Key: import:custFmyRelation:{taskId}
+Type: Hash
+Fields:
+ - taskId: "uuid"
+ - status: "SUCCESS" | "PARTIAL_SUCCESS" | "PROCESSING"
+ - totalCount: 100
+ - successCount: 95
+ - failureCount: 5
+ - progress: 100
+ - startTime: 1234567890
+ - endTime: 1234567900
+ - message: "成功95条,失败5条"
+TTL: 7 天
+```
+
+### 3. 导入日志记录
+使用 `ImportLogUtils` 统一记录:
+- ✅ 导入开始/结束
+- ✅ 批量查询日志
+- ✅ 进度跟踪
+- ✅ 验证错误详情
+- ✅ 批量操作日志
+- ✅ Redis 操作日志
+
+### 4. 数据验证增强
+- ✅ 身份证号格式验证(18位)
+- ✅ 字段长度验证
+- ✅ Excel 内部重复检查
+- ✅ 数据库唯一性检查(批量)
+
+---
+
+## 测试指南
+
+### 测试环境准备
+1. 启动后端服务 (`mvn spring-boot:run`)
+2. 确保数据库连接正常
+3. 确保 Redis 服务运行
+
+### 测试步骤
+
+#### 1. 下载导入模板
+```bash
+POST /ccdi/custFmyRelation/importTemplate
+Headers:
+ Authorization: Bearer {token}
+```
+
+**预期结果**:
+- 返回 Excel 文件
+- 包含字典下拉框(关系类型、性别、证件类型)
+
+#### 2. 准备测试数据
+
+创建包含以下字段的测试数据:
+
+| 信贷客户身份证号 | 关系类型 | 关系人姓名 | 性别 | 关系人证件类型 | 关系人证件号码 |
+|----------------|---------|-----------|------|-------------|-------------|
+| 110101199001011234 | 配偶 | 张三 | 男 | 身份证 | 110101199001011235 |
+| 110101199001011234 | 子女 | 李四 | 女 | 身份证 | 110101201001011236 |
+
+**测试场景**:
+- ✅ 正常数据导入
+- ✅ 重复数据导入(应返回错误)
+- ✅ Excel 内部重复(应检测并报错)
+- ✅ 必填字段缺失(应返回详细错误)
+
+#### 3. 提交导入任务
+```bash
+POST /ccdi/custFmyRelation/importData
+Headers:
+ Authorization: Bearer {token}
+Form Data:
+ file: 测试数据.xlsx
+```
+
+**预期响应**:
+```json
+{
+ "code": 200,
+ "msg": "导入任务已提交,正在后台处理",
+ "data": {
+ "taskId": "uuid-string",
+ "status": "PROCESSING",
+ "message": "导入任务已提交,正在后台处理"
+ }
+}
+```
+
+#### 4. 查询导入状态
+```bash
+GET /ccdi/custFmyRelation/importStatus/{taskId}
+Headers:
+ Authorization: Bearer {token}
+```
+
+**预期响应**:
+```json
+{
+ "taskId": "uuid-string",
+ "status": "SUCCESS",
+ "totalCount": 2,
+ "successCount": 2,
+ "failureCount": 0,
+ "progress": 100,
+ "message": "全部成功!共导入2条数据"
+}
+```
+
+#### 5. 查询失败记录
+```bash
+GET /ccdi/custFmyRelation/importFailures/{taskId}?pageNum=1&pageSize=10
+Headers:
+ Authorization: Bearer {token}
+```
+
+**预期响应** (如果有失败):
+```json
+{
+ "total": 1,
+ "rows": [
+ {
+ "rowNum": 2,
+ "personId": "110101199001011234",
+ "relationType": "配偶",
+ "relationName": "张三",
+ "errorMessage": "该关系已存在,请勿重复导入"
+ }
+ ],
+ "code": 200,
+ "msg": "查询成功"
+}
+```
+
+### 自动化测试脚本
+
+使用提供的测试脚本:
+```bash
+doc\test-scripts\test-cust-fmy-relation-import.bat
+```
+
+**测试脚本功能**:
+1. 登录获取 token
+2. 下载导入模板
+3. 提交导入任务
+4. 查询导入状态
+5. 查询失败记录
+6. 测试查询接口
+
+---
+
+## 验证清单
+
+### 功能验证
+- [ ] 导入模板下载正常
+- [ ] 导入任务提交成功
+- [ ] 导入状态查询正常
+- [ ] 导入成功数据正确插入数据库
+- [ ] 重复数据被正确拦截
+- [ ] Excel 内部重复被检测
+- [ ] 失败记录正确保存到 Redis
+- [ ] 失败记录查询支持分页
+- [ ] 导入日志正常输出
+
+### 性能验证
+- [ ] 导入 100 条数据 < 5 秒
+- [ ] 导入 1000 条数据 < 20 秒
+- [ ] 批量查询只执行 1 次 SQL
+- [ ] Redis 状态更新及时
+
+### 日志验证
+- [ ] 导入开始日志
+- [ ] 批量查询日志
+- [ ] 进度日志
+- [ ] 验证错误日志
+- [ ] 批量操作日志
+- [ ] 导入完成日志
+
+---
+
+## API 文档更新
+
+### 导入相关接口
+
+#### 1. 下载导入模板
+```http
+POST /ccdi/custFmyRelation/importTemplate
+Authorization: Bearer {token}
+Content-Type: application/json
+
+Response: Excel 文件
+```
+
+#### 2. 提交导入任务
+```http
+POST /ccdi/custFmyRelation/importData
+Authorization: Bearer {token}
+Content-Type: multipart/form-data
+
+Form Data:
+ file: Excel 文件
+
+Response:
+{
+ "code": 200,
+ "msg": "导入任务已提交,正在后台处理",
+ "data": {
+ "taskId": "uuid",
+ "status": "PROCESSING",
+ "message": "导入任务已提交,正在后台处理"
+ }
+}
+```
+
+#### 3. 查询导入状态
+```http
+GET /ccdi/custFmyRelation/importStatus/{taskId}
+Authorization: Bearer {token}
+
+Response:
+{
+ "code": 200,
+ "data": {
+ "taskId": "uuid",
+ "status": "SUCCESS",
+ "totalCount": 100,
+ "successCount": 95,
+ "failureCount": 5,
+ "progress": 100,
+ "startTime": 1234567890,
+ "endTime": 1234567900,
+ "message": "成功95条,失败5条"
+ }
+}
+```
+
+#### 4. 查询导入失败记录
+```http
+GET /ccdi/custFmyRelation/importFailures/{taskId}?pageNum=1&pageSize=10
+Authorization: Bearer {token}
+
+Response:
+{
+ "code": 200,
+ "total": 5,
+ "rows": [...],
+ "msg": "查询成功"
+}
+```
+
+---
+
+## 回归测试建议
+
+### 测试场景
+1. **正常数据导入**: 全部字段完整有效
+2. **必填字段缺失**: 缺少 personId、relationType 等
+3. **格式错误**: 身份证号格式不正确
+4. **数据重复**:
+ - 数据库中已存在
+ - Excel 文件内重复
+5. **大数据量**: 导入 1000+ 条数据
+6. **并发导入**: 同时提交多个导入任务
+7. **边界情况**: 空文件、单条数据、最大字段长度
+
+### 性能基准
+| 数据量 | 预期时间 | 最大内存 |
+|--------|---------|---------|
+| 10 条 | < 2 秒 | < 50MB |
+| 100 条 | < 5 秒 | < 100MB |
+| 1000 条 | < 20 秒 | < 200MB |
+| 10000 条 | < 3 分钟 | < 500MB |
+
+---
+
+## 注意事项
+
+### 1. 字典配置
+确保以下字典数据已配置:
+- `ccdi_relation_type` (关系类型)
+- `ccdi_indiv_gender` (性别)
+- `ccdi_certificate_type` (证件类型)
+
+### 2. Redis 配置
+- 确保 Redis 服务运行
+- 检查 Redis 过期策略
+- 监控 Redis 内存使用
+
+### 3. 异步配置
+- 确保 `@EnableAsync` 已启用
+- 检查异步线程池配置
+- 监控异步任务执行情况
+
+### 4. 日志级别
+- 生产环境: INFO
+- 开发环境: DEBUG
+- 测试环境: DEBUG
+
+---
+
+## 后续优化建议
+
+### 1. 导入进度实时推送
+考虑使用 WebSocket 实现导入进度实时推送,替代轮询查询。
+
+### 2. 导入历史记录
+添加导入历史记录表,记录每次导入的详细信息,便于追溯。
+
+### 3. 数据预校验
+在前端添加数据预校验,提前发现格式错误,减少无效提交。
+
+### 4. 导入模板智能生成
+根据数据库字典动态生成导入模板,减少维护成本。
+
+### 5. 批量操作优化
+考虑使用 MyBatis Plus 的 `SqlInjector` 实现真正的批量插入。
+
+---
+
+## 创建日期
+
+2026-02-11
+
+## 相关文档
+
+- [设计方案](../plans/2026-02-11-cust-fmy-relation-import-alignment.md)
+- [测试脚本](./test-cust-fmy-relation-import.bat)
+- [API 文档](../../api/ccdi/cust-fmy-relation-api.md)
diff --git a/doc/test-scripts/test-cust-fmy-relation-import.bat b/doc/test-scripts/test-cust-fmy-relation-import.bat
new file mode 100644
index 0000000..c399ad5
--- /dev/null
+++ b/doc/test-scripts/test-cust-fmy-relation-import.bat
@@ -0,0 +1,107 @@
+@echo off
+REM 信贷客户家庭关系导入功能测试脚本
+REM 测试对齐后的导入功能
+
+echo ========================================
+echo 信贷客户家庭关系导入功能测试
+echo ========================================
+echo.
+
+REM 设置后端服务地址
+set BASE_URL=http://localhost:8080
+
+REM 步骤1: 登录获取token
+echo [1/6] 正在登录...
+curl -s -X POST "%BASE_URL%/login/test" ^
+ -H "Content-Type: application/json" ^
+ -d "{\"username\":\"admin\",\"password\":\"admin123\"}" ^
+ > login_response.json
+
+REM 提取token
+for /f "tokens=2 delims=:\"" %%a in ('findstr /C:"\"token\"" login_response.json') do (
+ set TOKEN=%%a
+ goto :token_found
+)
+:token_found
+
+echo 登录成功! Token: %TOKEN:~0,20%...
+echo.
+
+REM 步骤2: 下载导入模板
+echo [2/6] 下载导入模板...
+curl -s -X POST "%BASE_URL%/ccdi/custFmyRelation/importTemplate" ^
+ -H "Authorization: Bearer %TOKEN%" ^
+ --output 信贷客户家庭关系导入模板.xlsx
+echo 模板已下载: 信贷客户家庭关系导入模板.xlsx
+echo.
+
+REM 步骤3: 测试导入接口(使用测试数据)
+echo [3/6] 测试导入接口...
+echo 创建测试Excel文件...
+
+REM 步骤4: 提交导入任务
+echo [4/6] 提交导入任务...
+curl -s -X POST "%BASE_URL%/ccdi/custFmyRelation/importData" ^
+ -H "Authorization: Bearer %TOKEN%" ^
+ -F "file=@测试数据_信贷客户家庭关系.xlsx" ^
+ > import_response.json
+
+echo 导入响应:
+type import_response.json
+echo.
+
+REM 提取taskId
+for /f "tokens=2 delims=:\"" %%a in ('findstr /C:"\"taskId\"" import_response.json') do (
+ set TASK_ID=%%a
+ goto :task_found
+)
+:task_found
+echo 任务ID: %TASK_ID%
+echo.
+
+REM 步骤5: 查询导入状态
+echo [5/6] 查询导入状态(等待3秒)...
+timeout /t 3 /nobreak >nul
+
+curl -s -X GET "%BASE_URL%/ccdi/custFmyRelation/importStatus/%TASK_ID%" ^
+ -H "Authorization: Bearer %TOKEN%" ^
+ > status_response.json
+
+echo 导入状态:
+type status_response.json
+echo.
+
+REM 步骤6: 查询导入失败记录
+echo [6/6] 查询导入失败记录...
+curl -s -X GET "%BASE_URL%/ccdi/custFmyRelation/importFailures/%TASK_ID%?pageNum=1&pageSize=10" ^
+ -H "Authorization: Bearer %TOKEN%" ^
+ > failures_response.json
+
+echo 失败记录:
+type failures_response.json
+echo.
+
+REM 测试查询接口
+echo [额外] 测试查询接口...
+curl -s -X GET "%BASE_URL%/ccdi/custFmyRelation/list?pageNum=1&pageSize=10" ^
+ -H "Authorization: Bearer %TOKEN%" ^
+ > list_response.json
+
+echo 查询结果:
+type list_response.json
+echo.
+
+echo ========================================
+echo 测试完成!
+echo ========================================
+echo.
+echo 生成的文件:
+echo - login_response.json (登录响应)
+echo - import_response.json (导入响应)
+echo - status_response.json (状态响应)
+echo - failures_response.json (失败记录)
+echo - list_response.json (查询结果)
+echo - 信贷客户家庭关系导入模板.xlsx (导入模板)
+echo.
+
+pause
diff --git a/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md b/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md
new file mode 100644
index 0000000..269be46
--- /dev/null
+++ b/docs/plans/2026-02-11-cust-fmy-relation-import-alignment.md
@@ -0,0 +1,373 @@
+# 信贷客户家庭关系导入功能对齐方案
+
+## 概述
+
+本文档描述了如何将**信贷客户家庭关系**功能的导入实现完全对齐到**员工亲属关系**的成熟模式。
+
+**参考模板**: `CcdiStaffEnterpriseRelationImportServiceImpl`
+**修改对象**: `CcdiCustFmyRelationImportServiceImpl`
+
+## 设计目标
+
+1. 提升代码质量和可维护性
+2. 优化性能,避免 N+1 查询问题
+3. 改善用户体验,提供详细的导入进度和状态反馈
+4. 统一日志记录和错误处理机制
+
+## 架构调整
+
+### 1. 引入导入工具类
+
+复用 `ImportLogUtils` 进行统一的日志记录:
+- 导入开始/结束日志
+- 批量查询日志
+- 进度跟踪日志
+- 验证错误日志
+- 批量操作日志
+
+### 2. Redis 状态管理升级
+
+**现状**: 简单 String 值存储状态
+```
+"COMPLETED:10:5"
+```
+
+**优化**: Hash 结构存储详细状态
+```java
+{
+ "taskId": "uuid",
+ "status": "SUCCESS" | "PARTIAL_SUCCESS" | "PROCESSING",
+ "totalCount": 100,
+ "successCount": 95,
+ "failureCount": 5,
+ "progress": 100,
+ "startTime": 1234567890,
+ "endTime": 1234567900,
+ "message": "成功95条,失败5条"
+}
+```
+
+- 过期时间: 7 天
+- 失败记录: 单独 Key, JSON 序列化, 7 天过期
+
+### 3. 批量查询优化
+
+**实现 `batchExistsByCombinations` 方法**:
+- 提取所有 `person_id + relation_type + relation_cert_no` 组合
+- 一次性批量查询已存在的组合
+- 避免循环查询导致的 N+1 问题
+
+### 4. 导入结果封装
+
+创建/复用统一的 VO:
+- `ImportStatusVO`: 导入状态详情
+- `ImportResultVO`: 导入提交结果
+- `CustFmyRelationImportFailureVO`: 失败记录详情
+
+## 数据验证逻辑
+
+### 唯一性检查
+
+**优化前**: 每条记录单独查询
+```java
+for (excel : excels) {
+ CcdiCustFmyRelation existing = mapper.selectExistingRelations(...);
+ // N 次数据库查询
+}
+```
+
+**优化后**: 批量查询
+```java
+Set existingCombinations = getExistingCombinations(excels);
+// 1 次数据库查询
+
+for (excel : excels) {
+ String combination = excel.getPersonId() + "|" + ...;
+ if (existingCombinations.contains(combination)) {
+ throw new RuntimeException("该关系已存在");
+ }
+}
+```
+
+### Excel 内部重复检查
+
+```java
+Set processedCombinations = new HashSet<>();
+
+for (excel : excels) {
+ String combination = ...;
+
+ if (processedCombinations.contains(combination)) {
+ throw new RuntimeException("该关系在导入文件中重复");
+ }
+
+ processedCombinations.add(combination);
+}
+```
+
+### 验证规则
+
+**必填字段**:
+- 信贷客户身份证号
+- 关系类型
+- 关系人姓名
+- 关系人证件类型
+- 关系人证件号码
+
+**格式验证**:
+- 身份证号: 18位有效格式
+- 证件号码: 根据证件类型验证
+
+**长度限制**:
+- 关系人姓名: ≤ 50
+- 关系类型: ≤ 20
+- 证件号码: ≤ 50
+
+## 批量操作优化
+
+### 分批插入策略
+
+```java
+private void saveBatch(List list, int batchSize) {
+ for (int i = 0; i < list.size(); i += batchSize) {
+ int end = Math.min(i + batchSize, list.size());
+ List subList = list.subList(i, end);
+ mapper.insertBatch(subList);
+ }
+}
+
+// 调用: 每 500 条为一批
+saveBatch(newRecords, 500);
+```
+
+### 批量操作日志
+
+```
+开始批量插入: 总批次数=5, 每批大小=500
+批量插入完成: 成功=2500, 耗时=1234ms
+```
+
+## 失败记录处理
+
+### 失败记录数据结构
+
+```java
+public class CustFmyRelationImportFailureVO {
+ private Integer rowNum; // Excel 行号
+ private String personId; // 信贷客户身份证号
+ private String relationType; // 关系类型
+ private String relationName; // 关系人姓名
+ private String errorMessage; // 错误消息
+}
+```
+
+### Redis 存储优化
+
+**Key**: `import:custFmyRelation:{taskId}:failures`
+**序列化**: JSON
+**过期时间**: 7 天
+**反序列化**:
+```java
+return JSON.parseArray(
+ JSON.toJSONString(failuresObj),
+ CustFmyRelationImportFailureVO.class
+);
+```
+
+## Controller 层调整
+
+### 导入接口
+
+```java
+@PostMapping("/importData")
+public AjaxResult importData(@RequestParam("file") MultipartFile file) {
+ List excels =
+ EasyExcelUtil.importExcel(file.getInputStream(), ...);
+
+ if (excels == null || excels.isEmpty()) {
+ return error("至少需要一条数据");
+ }
+
+ String taskId = relationService.importRelations(excels);
+
+ ImportResultVO result = new ImportResultVO();
+ result.setTaskId(taskId);
+ result.setStatus("PROCESSING");
+ result.setMessage("导入任务已提交,正在后台处理");
+
+ return AjaxResult.success("导入任务已提交,正在后台处理", result);
+}
+```
+
+### 导入状态查询
+
+```java
+@GetMapping("/importStatus/{taskId}")
+public AjaxResult getImportStatus(@PathVariable String taskId) {
+ ImportStatusVO statusVO = relationImportService.getImportStatus(taskId);
+ return success(statusVO);
+}
+```
+
+### 失败记录查询
+
+```java
+@GetMapping("/importFailures/{taskId}")
+public TableDataInfo getImportFailures(
+ @PathVariable String taskId,
+ @RequestParam(defaultValue = "1") Integer pageNum,
+ @RequestParam(defaultValue = "10") Integer pageSize
+) {
+ List failures =
+ relationImportService.getImportFailures(taskId);
+
+ // 手动分页
+ int fromIndex = (pageNum - 1) * pageSize;
+ int toIndex = Math.min(fromIndex + pageSize, failures.size());
+
+ if (fromIndex >= failures.size()) {
+ return getDataTable(new ArrayList<>(), failures.size());
+ }
+
+ List pageData =
+ failures.subList(fromIndex, toIndex);
+
+ return getDataTable(pageData, failures.size());
+}
+```
+
+## 导入模板改进
+
+### 使用字典下拉框
+
+```java
+@PostMapping("/importTemplate")
+public void importTemplate(HttpServletResponse response) {
+ EasyExcelUtil.importTemplateWithDictDropdown(
+ response,
+ CcdiCustFmyRelationExcel.class,
+ "信贷客户家庭关系"
+ );
+}
+```
+
+### Excel 实体注解增强
+
+```java
+@DictDropdown(type = "ccdi_relation_type")
+private String relationType;
+
+@DictDropdown(type = "ccdi_cert_type")
+private String relationCertType;
+```
+
+## 修改文件清单
+
+### 1. Service 层
+- `CcdiCustFmyRelationImportServiceImpl.java` - 核心导入逻辑重构
+- `CcdiCustFmyRelationServiceImpl.java` - 导入入口方法调整
+
+### 2. Controller 层
+- `CcdiCustFmyRelationController.java` - 接口返回值优化
+
+### 3. Mapper 层
+- `CcdiCustFmyRelationMapper.java` - 添加批量查询方法
+- Mapper XML - 实现批量查询 SQL
+
+### 4. VO 类
+- 检查/创建 `ImportStatusVO.java`
+- 检查/创建 `ImportResultVO.java`
+- 优化 `CustFmyRelationImportFailureVO.java`
+
+### 5. Excel 实体
+- `CcdiCustFmyRelationExcel.java` - 添加字典注解
+
+### 6. 工具类
+- 复用 `ImportLogUtils.java`
+- 复用 `EasyExcelUtil.java`
+
+## 关键代码片段
+
+### Mapper 批量查询
+
+```java
+// Mapper 接口
+List batchExistsByCombinations(
+ @Param("combinations") List combinations
+);
+
+// XML 实现
+
+```
+
+### 异步导入方法
+
+```java
+@Async
+@Transactional(rollbackFor = Exception.class)
+public void importRelationsAsync(
+ List excels,
+ String taskId,
+ String userName // 新增参数,用于审计
+) {
+ // 实现逻辑...
+}
+```
+
+## 实施步骤
+
+1. **添加 Mapper 批量查询方法**
+ - 在 Mapper 接口添加 `batchExistsByCombinations`
+ - 在 XML 实现 SQL
+
+2. **重构 ImportServiceImpl**
+ - 引入 `ImportLogUtils`
+ - 实现批量查询逻辑
+ - 添加 Excel 内部重复检查
+ - 优化 Redis 状态管理
+ - 改进失败记录存储
+
+3. **创建/优化 VO 类**
+ - 检查并复用已有的 `ImportStatusVO`
+ - 检查并复用已有的 `ImportResultVO`
+ - 优化失败记录 VO
+
+4. **调整 Controller**
+ - 修改导入接口返回值
+ - 优化状态查询接口
+ - 优化失败记录查询接口
+
+5. **更新 Excel 实体**
+ - 添加 `@DictDropdown` 注解
+
+6. **测试验证**
+ - 单元测试
+ - 集成测试
+ - 性能对比测试
+
+## 预期效果
+
+### 性能提升
+- 批量查询: 从 N 次减少到 1 次
+- 导入 1000 条数据预计提升 50-70%
+
+### 用户体验
+- 实时进度反馈
+- 详细的错误信息
+- 清晰的成功/失败统计
+
+### 代码质量
+- 统一的日志记录
+- 完善的错误处理
+- 更好的可维护性
+
+## 创建日期
+
+2026-02-11