# 银行流水实体类实现计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **目标:** 创建 CcdiBankStatement 实体类,实现从流水分析接口响应到数据库实体的数据转换功能。 **架构:** 在 ccdi-project 模块中创建实体类,使用 Spring BeanUtils 进行对象映射,手动处理字段名不一致的情况。实体类包含静态转换方法 fromResponse()。 **技术栈:** MyBatis Plus 3.5.10, Spring BeanUtils, Lombok, MySQL 8.2.0 --- ## 任务概览 | 任务 | 预估时间 | 文件 | |------|---------|------| | Task 1: 数据库表修改 | 5分钟 | SQL脚本 | | Task 2: 创建实体类基础结构 | 10分钟 | CcdiBankStatement.java | | Task 3: 编写转换方法测试 | 15分钟 | CcdiBankStatementTest.java | | Task 4: 实现转换方法 | 10分钟 | CcdiBankStatement.java | | Task 5: 创建 Mapper 接口 | 5分钟 | CcdiBankStatementMapper.java | | Task 6: 创建 Mapper XML | 10分钟 | CcdiBankStatementMapper.xml | | Task 7: 验证测试 | 5分钟 | - | --- ## Task 1: 数据库表修改 **目标:** 在 ccdi_bank_statement 表中添加 project_id 字段和索引。 **文件:** - 创建: `sql/ccdi_bank_statement_add_project_id.sql` **Step 1: 创建数据库修改脚本** ```sql -- 为 ccdi_bank_statement 表添加 project_id 字段 ALTER TABLE `ccdi_bank_statement` ADD COLUMN `project_id` bigint(20) DEFAULT NULL COMMENT '关联项目ID' AFTER `bank_statement_id`, ADD INDEX `idx_project_id` (`project_id`); ``` **Step 2: 执行数据库修改脚本** ```bash # 连接到数据库并执行脚本 mysql -h 116.62.17.81 -u root -p ccdi < sql/ccdi_bank_statement_add_project_id.sql ``` **预期输出:** 执行成功,无错误信息。 **Step 3: 验证字段已添加** ```sql -- 查看表结构,确认 project_id 字段已添加 SHOW COLUMNS FROM ccdi_bank_statement LIKE 'project_id'; ``` **预期输出:** ``` Field | Type | Null | Key | Default | Extra -------------|------------|------|-----|---------|------- project_id | bigint(20) | YES | MUL | NULL | ``` **Step 4: 提交** ```bash git add sql/ccdi_bank_statement_add_project_id.sql git commit -m "feat: 为银行流水表添加 project_id 字段" ``` --- ## Task 2: 创建实体类基础结构 **目标:** 创建 CcdiBankStatement 实体类的基础结构,包含所有字段定义。 **文件:** - 创建: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java` **Step 1: 创建实体类文件** ```java package com.ruoyi.ccdi.project.domain.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; /** * 银行流水对象 ccdi_bank_statement * * @author ruoyi * @date 2026-03-04 */ @Data @TableName("ccdi_bank_statement") public class CcdiBankStatement implements Serializable { @Serial private static final long serialVersionUID = 1L; // ===== 主键和关联字段 ===== /** 流水ID */ @TableId(type = IdType.AUTO) private Long bankStatementId; /** 关联项目ID(业务字段) */ private Long projectId; /** 企业ID */ private Integer leId; /** 账号ID */ private Long accountId; /** 项目id(保留原有字段) */ private Integer groupId; // ===== 账号信息 ===== /** 企业账号名称 */ private String leAccountName; /** 企业银行账号 */ private String leAccountNo; /** 账号日期ID */ private Integer accountingDateId; /** 账号日期 */ private String accountingDate; /** 交易日期 */ private String trxDate; /** 币种 */ private String currency; // ===== 交易金额 ===== /** 付款金额 */ private BigDecimal amountDr; /** 收款金额 */ private BigDecimal amountCr; /** 余额 */ private BigDecimal amountBalance; // ===== 交易类型和标志 ===== /** 交易类型 */ private String cashType; /** 交易标志位 */ private String trxFlag; /** 分类ID */ private Integer trxType; /** 异常类型 */ private String exceptionType; /** 是否为内部交易 */ private Integer internalFlag; // ===== 对手方信息 ===== /** 对手方企业ID */ private Integer customerLeId; /** 对手方企业名称 */ private String customerAccountName; /** 对手方账号 */ private String customerAccountNo; /** 对手方银行 */ private String customerBank; /** 对手方备注 */ private String customerReference; // ===== 摘要和备注 ===== /** 用户交易摘要 */ private String userMemo; /** 银行交易摘要 */ private String bankComments; /** 银行交易号 */ private String bankTrxNumber; // ===== 银行信息 ===== /** 所属银行缩写 */ private String bank; // ===== 批次和上传信息 ===== /** 上传logId */ private Integer batchId; /** 每次上传在文件中的line */ private Integer batchSequence; // ===== 附加字段 ===== /** meta json(固定为null) */ private String metaJson; /** 是否包含余额 */ private Integer noBalance; /** 初始余额 */ private Integer beginBalance; /** 结束余额 */ private Integer endBalance; /** 覆盖标识 */ private Long overrideBsId; /** 交易方式 */ private String paymentMethod; /** 身份证号 */ private String cretNo; // ===== 审计字段 ===== /** 创建时间 */ private Date createDate; /** 创建者 */ private Long createdBy; } ``` **Step 2: 验证代码编译** ```bash cd ccdi-project mvn compile ``` **预期输出:** BUILD SUCCESS **Step 3: 提交** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java git commit -m "feat: 创建银行流水实体类基础结构" ``` --- ## Task 3: 编写转换方法测试 **目标:** 使用 TDD 方法,先编写 fromResponse() 方法的单元测试。 **文件:** - 创建: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatementTest.java` **Step 1: 添加测试依赖(如果不存在)** 检查 `ccdi-project/pom.xml` 是否包含测试依赖: ```xml org.springframework.boot spring-boot-starter-test test ``` 如果没有,添加上述依赖。 **Step 2: 创建测试类** ```java package com.ruoyi.ccdi.project.domain.entity; import com.ruoyi.lsfx.domain.response.GetBankStatementResponse.BankStatementItem; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.*; /** * 银行流水实体类测试 * * @author ruoyi * @date 2026-03-04 */ class CcdiBankStatementTest { @Test void testFromResponse_Success() { // 准备测试数据 BankStatementItem item = new BankStatementItem(); item.setBankStatementId(123456L); item.setLeId(100); item.setAccountId(200L); item.setLeName("测试企业"); item.setAccountMaskNo("6222****1234"); item.setDrAmount(new BigDecimal("1000.00")); item.setCrAmount(new BigDecimal("500.00")); item.setBalanceAmount(new BigDecimal("5000.00")); item.setTrxDate("2026-03-04"); item.setCustomerAccountMaskNo("6228****5678"); item.setUploadSequnceNumber(1); // 执行转换 CcdiBankStatement entity = CcdiBankStatement.fromResponse(item); // 验证结果 assertNotNull(entity, "转换结果不应为null"); assertEquals(123456L, entity.getBankStatementId(), "流水ID应该匹配"); assertEquals(100, entity.getLeId(), "企业ID应该匹配"); assertEquals(200L, entity.getAccountId(), "账号ID应该匹配"); assertEquals("测试企业", entity.getLeAccountName(), "企业名称应该匹配"); // 验证手动映射的字段 assertEquals("6222****1234", entity.getLeAccountNo(), "企业账号应该从 accountMaskNo 映射"); assertEquals("6228****5678", entity.getCustomerAccountNo(), "对手方账号应该从 customerAccountMaskNo 映射"); assertEquals(1, entity.getBatchSequence(), "批次序号应该从 uploadSequnceNumber 映射"); // 验证金额字段 assertEquals(new BigDecimal("1000.00"), entity.getAmountDr(), "付款金额应该匹配"); assertEquals(new BigDecimal("500.00"), entity.getAmountCr(), "收款金额应该匹配"); assertEquals(new BigDecimal("5000.00"), entity.getAmountBalance(), "余额应该匹配"); // 验证特殊字段 assertNull(entity.getMetaJson(), "metaJson 应该强制为 null"); assertNull(entity.getProjectId(), "projectId 应该为 null(需要 Service 层设置)"); } @Test void testFromResponse_Null() { // 测试空值处理 CcdiBankStatement entity = CcdiBankStatement.fromResponse(null); // 验证返回 null assertNull(entity, "传入 null 应该返回 null"); } @Test void testFromResponse_EmptyObject() { // 测试空对象转换 BankStatementItem item = new BankStatementItem(); // 执行转换 CcdiBankStatement entity = CcdiBankStatement.fromResponse(item); // 验证不会抛出异常 assertNotNull(entity, "空对象转换结果不应为 null"); assertNull(entity.getMetaJson(), "metaJson 应该为 null"); } @Test void testFromResponse_FieldTypeCompatibility() { // 测试字段类型兼容性 BankStatementItem item = new BankStatementItem(); item.setInternalFlag(1); // Integer 类型 item.setTransTypeId(100); // Integer 类型 // 执行转换 CcdiBankStatement entity = CcdiBankStatement.fromResponse(item); // 验证类型转换正确 assertNotNull(entity, "转换结果不应为 null"); assertEquals(1, entity.getInternalFlag(), "Integer 类型应该正确复制"); assertEquals(100, entity.getTrxType(), "Integer 类型应该正确复制"); } } ``` **Step 3: 运行测试验证失败** ```bash cd ccdi-project mvn test -Dtest=CcdiBankStatementTest ``` **预期输出:** 编译失败,因为 `fromResponse()` 方法还不存在。 **Step 4: 提交** ```bash git add ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatementTest.java git commit -m "test: 添加银行流水转换方法的单元测试" ``` --- ## Task 4: 实现转换方法 **目标:** 在 CcdiBankStatement 实体类中实现 fromResponse() 静态方法。 **文件:** - 修改: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java` **Step 1: 添加必要的导入** 在文件顶部添加: ```java import com.ruoyi.lsfx.domain.response.GetBankStatementResponse.BankStatementItem; import org.springframework.beans.BeanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; ``` **Step 2: 添加日志常量** 在类的开头添加: ```java private static final Logger log = LoggerFactory.getLogger(CcdiBankStatement.class); ``` **Step 3: 实现 fromResponse() 方法** 在类的末尾(createdBy 字段之后)添加转换方法: ```java /** * 从流水分析接口响应转换为实体 * * @param item 流水分析接口返回的流水项 * @return 流水实体,如果 item 为 null 则返回 null */ public static CcdiBankStatement fromResponse(BankStatementItem item) { // 1. 空值检查 if (item == null) { log.warn("流水项为空,无法转换"); return null; } try { // 2. 创建实体对象 CcdiBankStatement entity = new CcdiBankStatement(); // 3. 使用 BeanUtils 复制同名字段 BeanUtils.copyProperties(item, entity); // 4. 手动映射字段名不一致的情况 entity.setLeAccountNo(item.getAccountMaskNo()); entity.setCustomerAccountNo(item.getCustomerAccountMaskNo()); entity.setBatchSequence(item.getUploadSequnceNumber()); // 5. 特殊字段处理 entity.setMetaJson(null); // 根据文档要求强制设为 null // 注意:project_id 需要在 Service 层根据业务逻辑设置 return entity; } catch (Exception e) { log.error("流水数据转换失败, bankStatementId={}", item.getBankStatementId(), e); throw new RuntimeException("流水数据转换失败", e); } } ``` **Step 4: 运行测试验证通过** ```bash cd ccdi-project mvn test -Dtest=CcdiBankStatementTest ``` **预期输出:** ``` Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESS ``` **Step 5: 提交** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java git commit -m "feat: 实现银行流水转换方法 fromResponse()" ``` --- ## Task 5: 创建 Mapper 接口 **目标:** 创建 MyBatis Mapper 接口,继承 BaseMapper 并提供批量插入方法。 **文件:** - 创建: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java` **Step 1: 创建 Mapper 接口** ```java package com.ruoyi.ccdi.project.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement; import org.apache.ibatis.annotations.Param; import java.util.List; /** * 银行流水Mapper接口 * * @author ruoyi * @date 2026-03-04 */ public interface CcdiBankStatementMapper extends BaseMapper { /** * 批量插入银行流水 * * @param list 银行流水列表 * @return 插入记录数 */ int insertBatch(@Param("list") List list); } ``` **Step 2: 验证代码编译** ```bash cd ccdi-project mvn compile ``` **预期输出:** BUILD SUCCESS **Step 3: 提交** ```bash git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java git commit -m "feat: 创建银行流水 Mapper 接口" ``` --- ## Task 6: 创建 Mapper XML **目标:** 创建 MyBatis XML 映射文件,实现批量插入 SQL。 **文件:** - 创建: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml` **Step 1: 创建 XML 文件** ```xml select bank_statement_id, project_id, LE_ID, ACCOUNT_ID, group_id, LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE, TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE, CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO, customer_bank, customer_reference, USER_MEMO, BANK_COMMENTS, BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE, internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by, meta_json, no_balance, begin_balance, end_balance, override_bs_id, payment_method, cret_no from ccdi_bank_statement insert into ccdi_bank_statement ( project_id, LE_ID, ACCOUNT_ID, group_id, LE_ACCOUNT_NAME, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, ACCOUNTING_DATE, TRX_DATE, CURRENCY, AMOUNT_DR, AMOUNT_CR, AMOUNT_BALANCE, CASH_TYPE, CUSTOMER_LE_ID, CUSTOMER_ACCOUNT_NAME, CUSTOMER_ACCOUNT_NO, customer_bank, customer_reference, USER_MEMO, BANK_COMMENTS, BANK_TRX_NUMBER, BANK, TRX_FLAG, TRX_TYPE, EXCEPTION_TYPE, internal_flag, batch_id, batch_sequence, CREATE_DATE, created_by, meta_json, no_balance, begin_balance, end_balance, override_bs_id, payment_method, cret_no ) values ( #{item.projectId}, #{item.leId}, #{item.accountId}, #{item.groupId}, #{item.leAccountName}, #{item.leAccountNo}, #{item.accountingDateId}, #{item.accountingDate}, #{item.trxDate}, #{item.currency}, #{item.amountDr}, #{item.amountCr}, #{item.amountBalance}, #{item.cashType}, #{item.customerLeId}, #{item.customerAccountName}, #{item.customerAccountNo}, #{item.customerBank}, #{item.customerReference}, #{item.userMemo}, #{item.bankComments}, #{item.bankTrxNumber}, #{item.bank}, #{item.trxFlag}, #{item.trxType}, #{item.exceptionType}, #{item.internalFlag}, #{item.batchId}, #{item.batchSequence}, #{item.createDate}, #{item.createdBy}, #{item.metaJson}, #{item.noBalance}, #{item.beginBalance}, #{item.endBalance}, #{item.overrideBsId}, #{item.paymentMethod}, #{item.cretNo} ) ``` **Step 2: 验证 XML 语法** ```bash cd ccdi-project mvn compile ``` **预期输出:** BUILD SUCCESS,无 XML 解析错误 **Step 3: 提交** ```bash git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml git commit -m "feat: 创建银行流水 Mapper XML 映射文件" ``` --- ## Task 7: 验证测试 **目标:** 运行所有测试,确保功能正常。 **Step 1: 运行单元测试** ```bash cd ccdi-project mvn test ``` **预期输出:** ``` Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESS ``` **Step 2: 运行集成编译** ```bash mvn clean compile ``` **预期输出:** BUILD SUCCESS **Step 3: 检查依赖关系** 确认 `ccdi-project` 模块的 `pom.xml` 中已依赖 `ccdi-lsfx` 模块: ```xml com.ruoyi ccdi-lsfx ``` 如果没有,添加上述依赖并重新编译。 **Step 4: 提交所有更改** ```bash git add . git commit -m "test: 完成银行流水实体类功能验证" ``` --- ## 完成检查清单 - [ ] 数据库已添加 `project_id` 字段和索引 - [ ] 实体类包含 39 个字段,类型正确 - [ ] `fromResponse()` 方法正确处理 3 个字段名映射 - [ ] `fromResponse()` 方法强制设置 `metaJson` 为 null - [ ] 单元测试覆盖正常转换、空值处理、字段映射等场景 - [ ] Mapper 接口继承 `BaseMapper` - [ ] Mapper XML 包含批量插入 SQL - [ ] 所有测试通过 --- ## 后续工作 本实施计划完成后,可以进行以下扩展: 1. **创建 Service 层** - 实现 `IBankStatementService` 接口和实现类 2. **创建 Controller 层** - 提供 REST API 接口 3. **编写集成测试** - 测试完整的数据库插入流程 4. **添加业务逻辑** - 在 Service 层设置 `projectId` 等业务字段 5. **性能优化** - 根据实际数据量调整批量插入大小 --- **计划版本:** 1.0 **创建日期:** 2026-03-04