Compare commits
13 Commits
2c9130538d
...
928e5ec2e1
| Author | SHA1 | Date | |
|---|---|---|---|
| 928e5ec2e1 | |||
| 5f207507de | |||
| 6ca5aa4812 | |||
| ac21ca1225 | |||
| a727119f51 | |||
| 638795e096 | |||
| 92ca798e99 | |||
| 4755e6fea3 | |||
| 4c9188bda9 | |||
| de98b25f93 | |||
| a1c9c18388 | |||
| dbaf7e97f8 | |||
| 8c1dfd2586 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -47,7 +47,12 @@ nul
|
|||||||
# Git Worktrees
|
# Git Worktrees
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
|
||||||
test/
|
# Test output directories (not source code)
|
||||||
|
**/target/test-classes/
|
||||||
|
**/target/surefire-reports/
|
||||||
|
|
||||||
|
# Test data files (keep test source code)
|
||||||
|
*.test.log
|
||||||
|
|
||||||
!*/build/*.java
|
!*/build/*.java
|
||||||
!*/build/*.html
|
!*/build/*.html
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
# 项目异步文件上传功能
|
# 项目异步文件上传功能
|
||||||
## 数据库
|
## 数据库
|
||||||
- 文件上传记录表:记录项目下所有文件的上传记录。项目id,流水分析平台的项目id,文件id,文件名称,文件大小,文件状态(上传中、解析中、解析成功、解析失败),主体名称,主体证件号,上传时间,上传人
|
- 文件上传记录表:记录项目下所有文件的上传记录。项目id,流水分析平台的项目id,文件id,文件名称,文件大小,文件状态(上传中、解析中、解析成功、解析失败),主体名称,主体账号,上传时间,上传人
|
||||||
|
|
||||||
## 流程
|
## 流程
|
||||||
- 在项目详情的上传数据页面,点击流水导入的上传流水按钮
|
- 在项目详情的上传数据页面,点击流水导入的上传流水按钮
|
||||||
- 批量选择文件,点击确认
|
- 批量选择文件,点击确认
|
||||||
- 每个文件都需要调接口传输到流水分析平台。建一个线程池,然后每个文件一个线程进行异步处理。处理流程如下
|
- 每个文件都需要调接口传输到流水分析平台。建一个线程池,然后每个文件一个线程进行异步处理。处理流程如下
|
||||||
1. 在文件上传表中插入一条该文件的记录,关联文件与项目,此时文件状态为上传中
|
1. 在文件上传表中插入一条该文件的记录,关联文件与项目和保存文件参数,此时文件状态为上传中
|
||||||
2. 调用流水分析平台的上传文件接口,
|
2. 调用流水分析平台的上传文件接口,获取返回参数中的logId,将状态更新为解析中并更新数据库
|
||||||
|
3. 轮询调用判断文件是否解析结束接口,间隔2秒,如果parsing为true继续,如果为false或者轮询达到300次则结束
|
||||||
|
4. 调用文件上传后获取单个文件上传后的状态接口,status等于-5且uploadStatusDesc等于data.wait.confirm.newaccount表示文件上传后解析成功,从返回值中获取enterpriseNameList更新到主体名称,accountNoList更新到主体账号,文件状态更新为解析成功;反之将文件状态更新为解析失败
|
||||||
|
5. 解析成功后,轮询调用获取流水列表并存储到兰溪本地接口,获取所有的流水,通过批量插入的方式保存到流水表中
|
||||||
|
|
||||||
|
## 设计
|
||||||
|
- 线程池容量为200个线程。如果线程池空闲线程不足,则提示系统繁忙稍后再试
|
||||||
|
- 方法中添加完善的日志
|
||||||
|
- 每次调用文件上传接口产生的日志单独生成一个日志文件,方便进行维护
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
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 com.ruoyi.lsfx.domain.response.GetBankStatementResponse.BankStatementItem;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CcdiBankStatement.class);
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从流水分析接口响应转换为实体
|
||||||
|
*
|
||||||
|
* @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.setLeAccountName(item.getLeName());
|
||||||
|
entity.setAmountDr(item.getDrAmount());
|
||||||
|
entity.setAmountCr(item.getCrAmount());
|
||||||
|
entity.setAmountBalance(item.getBalanceAmount());
|
||||||
|
entity.setTrxFlag(item.getTransFlag());
|
||||||
|
entity.setTrxType(item.getTransTypeId());
|
||||||
|
entity.setCustomerLeId(item.getCustomerId());
|
||||||
|
entity.setCustomerAccountName(item.getCustomerName());
|
||||||
|
|
||||||
|
// 5. 特殊字段处理
|
||||||
|
entity.setMetaJson(null); // 根据文档要求强制设为 null
|
||||||
|
|
||||||
|
// 注意:project_id 需要在 Service 层根据业务逻辑设置
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("流水数据转换失败, bankStatementId={}", item.getBankStatementId(), e);
|
||||||
|
throw new RuntimeException("流水数据转换失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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<CcdiBankStatement> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入银行流水
|
||||||
|
*
|
||||||
|
* @param list 银行流水列表
|
||||||
|
* @return 插入记录数
|
||||||
|
*/
|
||||||
|
int insertBatch(@Param("list") List<CcdiBankStatement> list);
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper">
|
||||||
|
|
||||||
|
<resultMap type="com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement" id="CcdiBankStatementResult">
|
||||||
|
<id property="bankStatementId" column="bank_statement_id" />
|
||||||
|
<result property="projectId" column="project_id" />
|
||||||
|
<result property="leId" column="LE_ID" />
|
||||||
|
<result property="accountId" column="ACCOUNT_ID" />
|
||||||
|
<result property="groupId" column="group_id" />
|
||||||
|
<result property="leAccountName" column="LE_ACCOUNT_NAME" />
|
||||||
|
<result property="leAccountNo" column="LE_ACCOUNT_NO" />
|
||||||
|
<result property="accountingDateId" column="ACCOUNTING_DATE_ID" />
|
||||||
|
<result property="accountingDate" column="ACCOUNTING_DATE" />
|
||||||
|
<result property="trxDate" column="TRX_DATE" />
|
||||||
|
<result property="currency" column="CURRENCY" />
|
||||||
|
<result property="amountDr" column="AMOUNT_DR" />
|
||||||
|
<result property="amountCr" column="AMOUNT_CR" />
|
||||||
|
<result property="amountBalance" column="AMOUNT_BALANCE" />
|
||||||
|
<result property="cashType" column="CASH_TYPE" />
|
||||||
|
<result property="customerLeId" column="CUSTOMER_LE_ID" />
|
||||||
|
<result property="customerAccountName" column="CUSTOMER_ACCOUNT_NAME" />
|
||||||
|
<result property="customerAccountNo" column="CUSTOMER_ACCOUNT_NO" />
|
||||||
|
<result property="customerBank" column="customer_bank" />
|
||||||
|
<result property="customerReference" column="customer_reference" />
|
||||||
|
<result property="userMemo" column="USER_MEMO" />
|
||||||
|
<result property="bankComments" column="BANK_COMMENTS" />
|
||||||
|
<result property="bankTrxNumber" column="BANK_TRX_NUMBER" />
|
||||||
|
<result property="bank" column="BANK" />
|
||||||
|
<result property="trxFlag" column="TRX_FLAG" />
|
||||||
|
<result property="trxType" column="TRX_TYPE" />
|
||||||
|
<result property="exceptionType" column="EXCEPTION_TYPE" />
|
||||||
|
<result property="internalFlag" column="internal_flag" />
|
||||||
|
<result property="batchId" column="batch_id" />
|
||||||
|
<result property="batchSequence" column="batch_sequence" />
|
||||||
|
<result property="createDate" column="CREATE_DATE" />
|
||||||
|
<result property="createdBy" column="created_by" />
|
||||||
|
<result property="metaJson" column="meta_json" />
|
||||||
|
<result property="noBalance" column="no_balance" />
|
||||||
|
<result property="beginBalance" column="begin_balance" />
|
||||||
|
<result property="endBalance" column="end_balance" />
|
||||||
|
<result property="overrideBsId" column="override_bs_id" />
|
||||||
|
<result property="paymentMethod" column="payment_method" />
|
||||||
|
<result property="cretNo" column="cret_no" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectCcdiBankStatementVo">
|
||||||
|
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
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<insert id="insertBatch" parameterType="java.util.List">
|
||||||
|
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
|
||||||
|
<foreach collection="list" item="item" separator=",">
|
||||||
|
(
|
||||||
|
#{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}
|
||||||
|
)
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
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");
|
||||||
|
|
||||||
|
// 执行转换
|
||||||
|
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(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 类型应该正确复制");
|
||||||
|
}
|
||||||
|
}
|
||||||
585
doc/design/2026-03-04-lsfx-interface-update-design.md
Normal file
585
doc/design/2026-03-04-lsfx-interface-update-design.md
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
# 流水分析接口功能更新设计文档
|
||||||
|
|
||||||
|
**文档版本**: v1.0
|
||||||
|
**创建日期**: 2026-03-04
|
||||||
|
**作者**: Claude Code
|
||||||
|
**状态**: 已批准
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 项目背景和需求分析
|
||||||
|
|
||||||
|
### 1.1 背景
|
||||||
|
|
||||||
|
根据最新的接口文档 `assets/对接流水分析/兰溪-流水分析对接3.md`,需要对现有的流水分析对接模块(ccdi-lsfx)进行更新。当前实现包含5个接口,缺少2个关键接口。
|
||||||
|
|
||||||
|
### 1.2 需求
|
||||||
|
|
||||||
|
**主要需求:**
|
||||||
|
1. 新增2个缺失的接口(获取单个文件上传状态、删除文件)
|
||||||
|
2. 更新所有现有接口以匹配最新文档规范
|
||||||
|
3. 保持与Mock Server的兼容性(使用当前配置)
|
||||||
|
4. 完善参数校验和错误处理
|
||||||
|
|
||||||
|
**当前实现状态:**
|
||||||
|
- ✅ 已实现: getToken, uploadFile, fetchInnerFlow, checkParseStatus, getBankStatement (5个)
|
||||||
|
- ❌ 缺失: getFileUploadStatus, deleteFiles (2个)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 架构设计
|
||||||
|
|
||||||
|
### 2.1 整体架构
|
||||||
|
|
||||||
|
```
|
||||||
|
ccdi-lsfx模块
|
||||||
|
├── client/
|
||||||
|
│ └── LsfxAnalysisClient.java (添加2个新方法,更新现有方法)
|
||||||
|
├── controller/
|
||||||
|
│ └── LsfxTestController.java (添加2个测试接口,更新现有接口)
|
||||||
|
├── domain/
|
||||||
|
│ ├── request/
|
||||||
|
│ │ ├── GetFileUploadStatusRequest.java (新增)
|
||||||
|
│ │ ├── DeleteFilesRequest.java (新增)
|
||||||
|
│ │ └── FetchInnerFlowRequest.java (更新)
|
||||||
|
│ └── response/
|
||||||
|
│ ├── GetFileUploadStatusResponse.java (新增)
|
||||||
|
│ └── DeleteFilesResponse.java (新增)
|
||||||
|
├── constants/
|
||||||
|
│ └── LsfxConstants.java (添加新常量)
|
||||||
|
├── util/
|
||||||
|
│ └── HttpUtil.java (添加GET请求支持)
|
||||||
|
└── config/ (通过application.yml配置)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 模块职责
|
||||||
|
|
||||||
|
| 层级 | 职责 | 变更 |
|
||||||
|
|------|------|------|
|
||||||
|
| Controller | 接口暴露、参数校验、响应封装 | 新增2个接口,更新3个接口 |
|
||||||
|
| Client | 业务逻辑封装、API调用 | 新增2个方法,更新4个方法 |
|
||||||
|
| Domain | 数据传输对象定义 | 新增4个DTO类 |
|
||||||
|
| Util | HTTP请求工具 | 新增GET方法 |
|
||||||
|
| Constants | 常量定义 | 新增固定值和状态常量 |
|
||||||
|
| Config | 配置管理 | 新增2个endpoint配置 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 数据模型设计
|
||||||
|
|
||||||
|
### 3.1 新增Request DTO
|
||||||
|
|
||||||
|
#### 3.1.1 GetFileUploadStatusRequest (接口5)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
public class GetFileUploadStatusRequest {
|
||||||
|
/** 项目ID (必填) */
|
||||||
|
private Integer groupId;
|
||||||
|
|
||||||
|
/** 文件ID (可选,不传则查询所有) */
|
||||||
|
private Integer logId;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明:**
|
||||||
|
- `groupId`: 必填,从getToken接口获取
|
||||||
|
- `logId`: 可选,不传则查询项目下所有文件
|
||||||
|
|
||||||
|
#### 3.1.2 DeleteFilesRequest (接口6)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
public class DeleteFilesRequest {
|
||||||
|
/** 项目ID (必填) */
|
||||||
|
private Integer groupId;
|
||||||
|
|
||||||
|
/** 文件ID数组 (必填) */
|
||||||
|
private Integer[] logIds;
|
||||||
|
|
||||||
|
/** 用户柜员号 (必填) */
|
||||||
|
private Integer userId;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明:**
|
||||||
|
- `logIds`: 支持批量删除,传递文件ID数组
|
||||||
|
- `userId`: 用于权限验证和审计
|
||||||
|
|
||||||
|
### 3.2 新增Response DTO
|
||||||
|
|
||||||
|
#### 3.2.1 GetFileUploadStatusResponse (接口5)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
public class GetFileUploadStatusResponse {
|
||||||
|
private String code;
|
||||||
|
private String status;
|
||||||
|
private Boolean successResponse;
|
||||||
|
private FileUploadStatusData data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FileUploadStatusData {
|
||||||
|
/** 日志列表 */
|
||||||
|
private List<LogItem> logs;
|
||||||
|
|
||||||
|
/** 状态 */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/** 账号ID */
|
||||||
|
private Integer accountId;
|
||||||
|
|
||||||
|
/** 币种 */
|
||||||
|
private String currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class LogItem {
|
||||||
|
// 完整字段定义见实施文档
|
||||||
|
// 关键字段:
|
||||||
|
private List<String> enterpriseNameList; // 主体名称列表
|
||||||
|
private List<String> accountNoList; // 账号列表
|
||||||
|
private Integer status; // 状态值(-5表示成功)
|
||||||
|
private String uploadStatusDesc; // 状态描述
|
||||||
|
private Integer logId; // 文件ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键字段说明:**
|
||||||
|
- `enterpriseNameList`: 仅有一个空字符串""时,表示未生成主体
|
||||||
|
- `status=-5` 且 `uploadStatusDesc="data.wait.confirm.newaccount"` 表示解析成功
|
||||||
|
|
||||||
|
#### 3.2.2 DeleteFilesResponse (接口6)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
public class DeleteFilesResponse {
|
||||||
|
private String code;
|
||||||
|
private String status;
|
||||||
|
private Boolean successResponse;
|
||||||
|
private DeleteFilesData data;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DeleteFilesData {
|
||||||
|
/** 删除成功消息 */
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 更新现有DTO
|
||||||
|
|
||||||
|
#### 3.3.1 FetchInnerFlowRequest (接口3)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
public class FetchInnerFlowRequest {
|
||||||
|
// ... 现有字段 ...
|
||||||
|
|
||||||
|
/** 校验码 (新增,固定值"ZJRCU") */
|
||||||
|
private String dataChannelCode;
|
||||||
|
|
||||||
|
// ... 其他字段 ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 常量定义
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class LsfxConstants {
|
||||||
|
// 固定值常量
|
||||||
|
public static final String DEFAULT_USER_ID = "902001";
|
||||||
|
public static final String DEFAULT_USER_NAME = "902001";
|
||||||
|
public static final String DEFAULT_APP_ID = "remote_app";
|
||||||
|
public static final String DEFAULT_ROLE = "VIEWER";
|
||||||
|
public static final String DEFAULT_ANALYSIS_TYPE = "-1";
|
||||||
|
public static final String DEFAULT_ORG_CODE = "902000";
|
||||||
|
public static final String DEFAULT_DEPARTMENT_CODE = "902000";
|
||||||
|
public static final String DEFAULT_DATA_CHANNEL_CODE = "ZJRCU";
|
||||||
|
|
||||||
|
// 状态常量
|
||||||
|
public static final Integer PARSE_STATUS_SUCCESS = -5;
|
||||||
|
public static final String PARSE_STATUS_DESC_SUCCESS = "data.wait.confirm.newaccount";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 接口详细设计
|
||||||
|
|
||||||
|
### 4.1 新增接口
|
||||||
|
|
||||||
|
#### 4.1.1 接口5: 获取单个文件上传状态
|
||||||
|
|
||||||
|
**接口信息:**
|
||||||
|
- 路径: `/watson/api/project/bs/upload`
|
||||||
|
- 方法: GET
|
||||||
|
- 请求头: `X-Xencio-Client-Id`
|
||||||
|
|
||||||
|
**请求参数:**
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| groupId | Integer | 是 | 项目ID |
|
||||||
|
| logId | Integer | 否 | 文件ID(不传则查询所有) |
|
||||||
|
|
||||||
|
**成功标识:**
|
||||||
|
- `status = -5`
|
||||||
|
- `uploadStatusDesc = "data.wait.confirm.newaccount"`
|
||||||
|
|
||||||
|
**使用场景:**
|
||||||
|
- 文件解析完成后获取主体名称和账号
|
||||||
|
- 判断是否需要生成主体(`enterpriseNameList`为空字符串)
|
||||||
|
|
||||||
|
#### 4.1.2 接口6: 删除文件
|
||||||
|
|
||||||
|
**接口信息:**
|
||||||
|
- 路径: `/watson/api/project/batchDeleteUploadFile`
|
||||||
|
- 方法: POST
|
||||||
|
- 请求头: `X-Xencio-Client-Id`
|
||||||
|
- Content-Type: multipart/form-data
|
||||||
|
|
||||||
|
**请求参数:**
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| groupId | Integer | 是 | 项目ID |
|
||||||
|
| logIds | Integer[] | 是 | 文件ID数组 |
|
||||||
|
| userId | Integer | 是 | 用户柜员号 |
|
||||||
|
|
||||||
|
**使用场景:**
|
||||||
|
- 文件解析失败后清理文件
|
||||||
|
- 删除错误上传的文件
|
||||||
|
- 批量删除不需要的文件
|
||||||
|
|
||||||
|
### 4.2 更新现有接口
|
||||||
|
|
||||||
|
#### 4.2.1 接口1: getToken
|
||||||
|
|
||||||
|
**更新内容:**
|
||||||
|
- 添加固定值默认值处理
|
||||||
|
- 简化必填参数校验
|
||||||
|
|
||||||
|
**默认值设置:**
|
||||||
|
```java
|
||||||
|
userId: "902001" (如果未传)
|
||||||
|
userName: "902001" (如果未传)
|
||||||
|
role: "VIEWER" (如果未传)
|
||||||
|
analysisType: "-1" (如果未传)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2.2 接口3: fetchInnerFlow
|
||||||
|
|
||||||
|
**更新内容:**
|
||||||
|
- 添加 `dataChannelCode` 字段
|
||||||
|
- 默认值: "ZJRCU"
|
||||||
|
|
||||||
|
#### 4.2.3 接口4: checkParseStatus
|
||||||
|
|
||||||
|
**更新内容:**
|
||||||
|
- 完善方法注释,添加轮询说明
|
||||||
|
- 明确成功状态码判断逻辑
|
||||||
|
|
||||||
|
**轮询说明:**
|
||||||
|
```
|
||||||
|
建议轮询间隔: 1秒
|
||||||
|
成功条件: parsing=false 且 status=-5 且 uploadStatusDesc="data.wait.confirm.newaccount"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2.4 接口2,5,7
|
||||||
|
|
||||||
|
**更新内容:**
|
||||||
|
- 完善Swagger文档注释
|
||||||
|
- 添加状态常量说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 完整调用流程
|
||||||
|
|
||||||
|
### 5.1 标准流程图
|
||||||
|
|
||||||
|
```
|
||||||
|
开始
|
||||||
|
↓
|
||||||
|
1. getToken - 创建项目获取Token
|
||||||
|
返回: token, projectId
|
||||||
|
↓
|
||||||
|
2a. uploadFile - 上传文件
|
||||||
|
或
|
||||||
|
2b. fetchInnerFlow - 拉取行内流水
|
||||||
|
返回: logId列表
|
||||||
|
↓
|
||||||
|
3. checkParseStatus - 检查解析状态(轮询)
|
||||||
|
建议: 每隔1秒轮询,直到parsing=false
|
||||||
|
↓
|
||||||
|
4. 判断解析结果
|
||||||
|
├─ 成功(status=-5 且 desc="data.wait.confirm.newaccount")
|
||||||
|
│ ↓
|
||||||
|
│ 5. getFileUploadStatus - 获取文件状态(可选)
|
||||||
|
│ 返回: enterpriseNameList, accountNoList
|
||||||
|
│ ↓
|
||||||
|
│ 6. getBankStatement - 获取银行流水
|
||||||
|
│ 返回: bankStatementList
|
||||||
|
│ ↓
|
||||||
|
│ 结束(成功)
|
||||||
|
│
|
||||||
|
└─ 失败(status != -5)
|
||||||
|
↓
|
||||||
|
7. deleteFiles - 删除文件
|
||||||
|
返回: 删除成功消息
|
||||||
|
↓
|
||||||
|
结束(失败)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 典型调用示例
|
||||||
|
|
||||||
|
#### 示例1: 文件上传流程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 获取Token
|
||||||
|
POST /lsfx/test/getToken
|
||||||
|
{
|
||||||
|
"projectNo": "902000_1709907600000",
|
||||||
|
"entityName": "902000_202603041430"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 响应: projectId=16238
|
||||||
|
|
||||||
|
# 2. 上传文件
|
||||||
|
POST /lsfx/test/uploadFile
|
||||||
|
groupId=16238, file=银行流水.csv
|
||||||
|
|
||||||
|
# 响应: logId=19135
|
||||||
|
|
||||||
|
# 3. 检查解析状态(轮询)
|
||||||
|
POST /lsfx/test/checkParseStatus
|
||||||
|
groupId=16238, inprogressList=19135
|
||||||
|
|
||||||
|
# 响应: parsing=false, status=-5
|
||||||
|
|
||||||
|
# 4. 获取文件状态
|
||||||
|
GET /lsfx/test/getFileUploadStatus
|
||||||
|
groupId=16238, logId=19135
|
||||||
|
|
||||||
|
# 响应: enterpriseNameList=["张三"], accountNoList=["1234567890"]
|
||||||
|
|
||||||
|
# 5. 获取银行流水
|
||||||
|
POST /lsfx/test/getBankStatement
|
||||||
|
{
|
||||||
|
"groupId": 16238,
|
||||||
|
"logId": 19135,
|
||||||
|
"pageNow": 1,
|
||||||
|
"pageSize": 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 示例2: 解析失败处理流程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 前面步骤相同...
|
||||||
|
|
||||||
|
# 3. 检查解析状态
|
||||||
|
响应: parsing=false, status=-1, uploadStatusDesc="data.parse.error"
|
||||||
|
|
||||||
|
# 4. 删除文件
|
||||||
|
POST /lsfx/test/deleteFiles
|
||||||
|
{
|
||||||
|
"groupId": 16238,
|
||||||
|
"logIds": [19135],
|
||||||
|
"userId": 902001
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 测试计划
|
||||||
|
|
||||||
|
### 6.1 单元测试用例
|
||||||
|
|
||||||
|
| ID | 测试场景 | 测试接口 | 测试数据 | 预期结果 |
|
||||||
|
|----|---------|---------|---------|---------|
|
||||||
|
| UT01 | 获取Token-成功 | getToken | 完整必填参数 | code=200, 返回token |
|
||||||
|
| UT02 | 获取Token-缺少参数 | getToken | 缺少projectNo | 返回错误提示 |
|
||||||
|
| UT03 | 上传文件-成功 | uploadFile | groupId, 有效CSV | code=200, 返回logId |
|
||||||
|
| UT04 | 上传文件-文件过大 | uploadFile | 超过10MB文件 | 返回文件超限错误 |
|
||||||
|
| UT05 | 拉取流水-成功 | fetchInnerFlow | customerNo, 日期范围 | code=200, 返回logId列表 |
|
||||||
|
| UT06 | 拉取流水-日期错误 | fetchInnerFlow | 开始>结束日期 | 返回日期错误提示 |
|
||||||
|
| UT07 | 检查状态-解析中 | checkParseStatus | 刚上传的文件 | parsing=true |
|
||||||
|
| UT08 | 检查状态-完成 | checkParseStatus | 解析完成的文件 | parsing=false, status=-5 |
|
||||||
|
| UT09 | 获取文件状态-单个 | getFileUploadStatus | groupId, logId | 返回文件状态信息 |
|
||||||
|
| UT10 | 获取文件状态-全部 | getFileUploadStatus | groupId, 不传logId | 返回所有文件状态 |
|
||||||
|
| UT11 | 删除文件-成功 | deleteFiles | groupId, logIds, userId | code=200, message=success |
|
||||||
|
| UT12 | 删除文件-缺少logIds | deleteFiles | 空logIds数组 | 返回错误提示 |
|
||||||
|
| UT13 | 获取流水-分页 | getBankStatement | pageNow=1, pageSize=10 | 返回10条记录 |
|
||||||
|
|
||||||
|
### 6.2 集成测试场景
|
||||||
|
|
||||||
|
**场景1: 完整文件上传流程**
|
||||||
|
```
|
||||||
|
getToken → uploadFile → checkParseStatus(轮询) → getFileUploadStatus → getBankStatement
|
||||||
|
```
|
||||||
|
|
||||||
|
**场景2: 完整行内流水流程**
|
||||||
|
```
|
||||||
|
getToken → fetchInnerFlow → checkParseStatus(轮询) → getFileUploadStatus → getBankStatement
|
||||||
|
```
|
||||||
|
|
||||||
|
**场景3: 解析失败处理流程**
|
||||||
|
```
|
||||||
|
uploadFile(错误文件) → checkParseStatus(失败) → deleteFiles
|
||||||
|
```
|
||||||
|
|
||||||
|
**场景4: 边界条件测试**
|
||||||
|
- 大文件上传(接近10MB)
|
||||||
|
- 大量数据分页获取(pageSize=1000)
|
||||||
|
- 并发多个文件上传
|
||||||
|
|
||||||
|
### 6.3 Mock Server测试
|
||||||
|
|
||||||
|
**需要更新的Mock接口:**
|
||||||
|
- `GET /watson/api/project/bs/upload` - 返回模拟文件状态
|
||||||
|
- `POST /watson/api/project/batchDeleteUploadFile` - 返回删除成功消息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 实施计划
|
||||||
|
|
||||||
|
### 7.1 实施阶段
|
||||||
|
|
||||||
|
**阶段一: 数据模型层 (1小时)**
|
||||||
|
- 创建4个新DTO类
|
||||||
|
- 更新1个现有DTO
|
||||||
|
- 更新常量类
|
||||||
|
|
||||||
|
**阶段二: 工具类增强 (30分钟)**
|
||||||
|
- 扩展HttpUtil支持GET请求
|
||||||
|
|
||||||
|
**阶段三: 客户端层 (1.5小时)**
|
||||||
|
- 新增2个Client方法
|
||||||
|
- 更新4个现有方法
|
||||||
|
|
||||||
|
**阶段四: 控制器层 (1小时)**
|
||||||
|
- 新增2个Controller接口
|
||||||
|
- 更新3个现有接口
|
||||||
|
|
||||||
|
**阶段五: 配置更新 (15分钟)**
|
||||||
|
- 更新application-dev.yml
|
||||||
|
|
||||||
|
**阶段六: Mock Server更新 (30分钟)**
|
||||||
|
- 更新Python Mock Server
|
||||||
|
|
||||||
|
**阶段七: 测试验证 (1小时)**
|
||||||
|
- 单元测试
|
||||||
|
- 集成测试
|
||||||
|
- Swagger文档测试
|
||||||
|
|
||||||
|
**阶段八: 文档编写 (30分钟)**
|
||||||
|
- API文档
|
||||||
|
- 测试报告
|
||||||
|
|
||||||
|
**总计: 约6小时**
|
||||||
|
|
||||||
|
### 7.2 文件清单
|
||||||
|
|
||||||
|
**新增文件 (8个):**
|
||||||
|
```
|
||||||
|
ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/request/
|
||||||
|
├── GetFileUploadStatusRequest.java
|
||||||
|
└── DeleteFilesRequest.java
|
||||||
|
|
||||||
|
ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/
|
||||||
|
├── GetFileUploadStatusResponse.java
|
||||||
|
└── DeleteFilesResponse.java
|
||||||
|
|
||||||
|
doc/
|
||||||
|
├── api-docs/lsfx-api-v3.md
|
||||||
|
├── design/2026-03-04-lsfx-interface-update-design.md
|
||||||
|
└── test-scripts/lsfx-test-report-20260304.md
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改文件 (7个):**
|
||||||
|
```
|
||||||
|
ccdi-lsfx/src/main/java/com/ruoyi/lsfx/
|
||||||
|
├── client/LsfxAnalysisClient.java
|
||||||
|
├── controller/LsfxTestController.java
|
||||||
|
├── constants/LsfxConstants.java
|
||||||
|
├── util/HttpUtil.java
|
||||||
|
└── domain/request/FetchInnerFlowRequest.java
|
||||||
|
|
||||||
|
ruoyi-admin/src/main/resources/
|
||||||
|
└── application-dev.yml
|
||||||
|
|
||||||
|
lsfx-mock-server/
|
||||||
|
└── app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 风险分析
|
||||||
|
|
||||||
|
### 8.1 技术风险
|
||||||
|
|
||||||
|
| 风险项 | 影响 | 概率 | 缓解措施 |
|
||||||
|
|--------|------|------|---------|
|
||||||
|
| GET请求参数传递方式不明确 | 中 | 低 | 参考文档示例,进行实际测试 |
|
||||||
|
| 数组参数传递格式问题 | 中 | 中 | 查阅HTTP规范,使用正确的传递方式 |
|
||||||
|
| 状态码判断逻辑错误 | 高 | 低 | 严格按文档实现,添加详细日志 |
|
||||||
|
|
||||||
|
### 8.2 兼容性风险
|
||||||
|
|
||||||
|
| 风险项 | 影响 | 概率 | 缓解措施 |
|
||||||
|
|--------|------|------|---------|
|
||||||
|
| 现有接口调用者受影响 | 中 | 低 | 保持接口签名兼容,只增加功能 |
|
||||||
|
| 配置变更需要重启 | 低 | 高 | 在文档中明确说明 |
|
||||||
|
|
||||||
|
### 8.3 测试风险
|
||||||
|
|
||||||
|
| 风险项 | 影响 | 概率 | 缓解措施 |
|
||||||
|
|--------|------|------|---------|
|
||||||
|
| Mock数据与真实接口不一致 | 中 | 中 | 严格按照文档构造Mock响应 |
|
||||||
|
| 边界条件未覆盖 | 中 | 中 | 设计全面的测试用例 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 验收标准
|
||||||
|
|
||||||
|
### 9.1 功能验收
|
||||||
|
|
||||||
|
- ✅ 所有7个接口都能正常调用
|
||||||
|
- ✅ 参数校验正确(必填、格式、范围)
|
||||||
|
- ✅ 响应格式符合文档定义
|
||||||
|
- ✅ 错误处理完善(异常捕获、日志记录)
|
||||||
|
|
||||||
|
### 9.2 代码质量
|
||||||
|
|
||||||
|
- ✅ 遵循项目编码规范
|
||||||
|
- ✅ 代码注释完整清晰
|
||||||
|
- ✅ 日志记录完善
|
||||||
|
- ✅ 无明显的性能问题
|
||||||
|
|
||||||
|
### 9.3 文档完整性
|
||||||
|
|
||||||
|
- ✅ API文档完整
|
||||||
|
- ✅ 测试报告完整
|
||||||
|
- ✅ 设计文档已保存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 附录
|
||||||
|
|
||||||
|
### 10.1 参考资料
|
||||||
|
|
||||||
|
- `assets/对接流水分析/兰溪-流水分析对接3.md` - 最新接口文档
|
||||||
|
- `CLAUDE.md` - 项目开发规范
|
||||||
|
- Spring Boot 3 文档
|
||||||
|
- MyBatis Plus 文档
|
||||||
|
|
||||||
|
### 10.2 变更历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 作者 | 变更内容 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| v1.0 | 2026-03-04 | Claude Code | 初始版本 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档结束**
|
||||||
603
docs/plans/2026-03-04-bank-statement-entity-design.md
Normal file
603
docs/plans/2026-03-04-bank-statement-entity-design.md
Normal file
@@ -0,0 +1,603 @@
|
|||||||
|
# 银行流水实体类与数据转换设计文档
|
||||||
|
|
||||||
|
**日期:** 2026-03-04
|
||||||
|
**模块:** ccdi-project
|
||||||
|
**作者:** Claude
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、概述
|
||||||
|
|
||||||
|
### 1.1 目标
|
||||||
|
|
||||||
|
创建银行流水实体类 `CcdiBankStatement`,用于持久化从流水分析平台获取的流水数据,并提供数据转换方法。
|
||||||
|
|
||||||
|
### 1.2 背景
|
||||||
|
|
||||||
|
- 流水分析平台提供 `GetBankStatementResponse.BankStatementItem` 接口响应对象
|
||||||
|
- 需要将响应数据转换为本地数据库实体进行持久化
|
||||||
|
- 流水数据需要关联到具体项目(`ccdi_project` 表)
|
||||||
|
|
||||||
|
### 1.3 技术选型
|
||||||
|
|
||||||
|
| 技术点 | 选择 | 理由 |
|
||||||
|
|--------|------|------|
|
||||||
|
| ORM框架 | MyBatis Plus 3.5.10 | 项目已集成,简化CRUD操作 |
|
||||||
|
| 对象映射 | Spring BeanUtils | 无需额外依赖,简单易用 |
|
||||||
|
| 数据库 | MySQL 8.2.0 | 项目标准数据库 |
|
||||||
|
| 实体类注解 | Lombok @Data | 简化代码,提高可读性 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、架构设计
|
||||||
|
|
||||||
|
### 2.1 模块位置
|
||||||
|
|
||||||
|
**主模块:** `ccdi-project` (项目管理模块)
|
||||||
|
|
||||||
|
**依赖关系:**
|
||||||
|
```
|
||||||
|
ccdi-project (流水实体类所在模块)
|
||||||
|
└── 依赖 ccdi-lsfx (访问流水分析响应类)
|
||||||
|
└── 依赖 ruoyi-common (通用工具)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 包结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ccdi-project/
|
||||||
|
├── src/main/java/com/ruoyi/ccdi/project/
|
||||||
|
│ ├── domain/
|
||||||
|
│ │ └── entity/
|
||||||
|
│ │ └── CcdiBankStatement.java (核心实体类)
|
||||||
|
│ ├── mapper/
|
||||||
|
│ │ └── CcdiBankStatementMapper.java (数据访问层)
|
||||||
|
│ └── service/
|
||||||
|
│ ├── IBankStatementService.java
|
||||||
|
│ └── impl/BankStatementServiceImpl.java
|
||||||
|
└── src/main/resources/
|
||||||
|
└── mapper/ccdi/project/
|
||||||
|
└── CcdiBankStatementMapper.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 核心组件
|
||||||
|
|
||||||
|
**1. 实体类:** `CcdiBankStatement`
|
||||||
|
- 39个字段(38个原有字段 + 1个新增字段)
|
||||||
|
- 包含静态转换方法 `fromResponse()`
|
||||||
|
- 使用 MyBatis Plus 注解进行映射
|
||||||
|
|
||||||
|
**2. Mapper接口:** `CcdiBankStatementMapper`
|
||||||
|
- 继承 `BaseMapper<CcdiBankStatement>`
|
||||||
|
- 提供批量插入方法
|
||||||
|
|
||||||
|
**3. Service层:** 调用转换方法,设置业务字段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、数据模型设计
|
||||||
|
|
||||||
|
### 3.1 数据库表结构修改
|
||||||
|
|
||||||
|
**表名:** `ccdi_bank_statement`
|
||||||
|
|
||||||
|
**新增字段:**
|
||||||
|
```sql
|
||||||
|
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`);
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明:**
|
||||||
|
- `project_id` 关联 `ccdi_project` 表的主键
|
||||||
|
- `group_id` 字段保留,用于兼容流水分析平台的原始项目ID
|
||||||
|
|
||||||
|
### 3.2 字段映射关系
|
||||||
|
|
||||||
|
**总字段数:** 39个
|
||||||
|
|
||||||
|
**字段分类:**
|
||||||
|
|
||||||
|
| 分类 | 字段数 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 主键和关联 | 4 | bank_statement_id, project_id, le_id, group_id |
|
||||||
|
| 账号信息 | 5 | account_id, le_account_name, le_account_no, accounting_date_id, accounting_date |
|
||||||
|
| 交易信息 | 5 | trx_date, currency, amount_dr, amount_cr, amount_balance |
|
||||||
|
| 交易类型 | 5 | cash_type, trx_flag, trx_type, exception_type, internal_flag |
|
||||||
|
| 对手方信息 | 5 | customer_le_id, customer_account_name, customer_account_no, customer_bank, customer_reference |
|
||||||
|
| 摘要备注 | 4 | user_memo, bank_comments, bank_trx_number, bank |
|
||||||
|
| 批次上传 | 2 | batch_id, batch_sequence |
|
||||||
|
| 附加字段 | 7 | meta_json, no_balance, begin_balance, end_balance, override_bs_id, payment_method, cret_no |
|
||||||
|
| 审计字段 | 2 | create_date, created_by |
|
||||||
|
|
||||||
|
**特殊字段处理:**
|
||||||
|
|
||||||
|
| 数据库字段 | 响应字段 | 处理方式 |
|
||||||
|
|-----------|---------|---------|
|
||||||
|
| le_account_no | accountMaskNo | 手动映射 |
|
||||||
|
| customer_account_no | customerAccountMaskNo | 手动映射 |
|
||||||
|
| batch_sequence | uploadSequnceNumber | 手动映射 |
|
||||||
|
| meta_json | - | 强制设为 null |
|
||||||
|
| project_id | - | Service层设置 |
|
||||||
|
|
||||||
|
### 3.3 实体类字段类型
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 数值类型
|
||||||
|
private Long bankStatementId; // 主键
|
||||||
|
private Long projectId; // 项目ID(新增)
|
||||||
|
private Long accountId; // 账号ID
|
||||||
|
private Integer leId; // 企业ID
|
||||||
|
private Integer groupId; // 项目ID(原有)
|
||||||
|
private Integer accountingDateId; // 账号日期ID
|
||||||
|
private Integer customerLeId; // 对手方企业ID
|
||||||
|
private Integer trxType; // 分类ID
|
||||||
|
private Integer internalFlag; // 内部交易标志
|
||||||
|
private Integer batchId; // 批次ID
|
||||||
|
private Integer batchSequence; // 批次序号
|
||||||
|
private Integer noBalance; // 是否包含余额
|
||||||
|
private Integer beginBalance; // 初始余额
|
||||||
|
private Integer endBalance; // 结束余额
|
||||||
|
private Long overrideBsId; // 覆盖标识
|
||||||
|
private Long createdBy; // 创建者
|
||||||
|
|
||||||
|
// 金额类型
|
||||||
|
private BigDecimal amountDr; // 付款金额
|
||||||
|
private BigDecimal amountCr; // 收款金额
|
||||||
|
private BigDecimal amountBalance; // 余额
|
||||||
|
|
||||||
|
// 字符串类型
|
||||||
|
private String leAccountName; // 企业账号名称
|
||||||
|
private String leAccountNo; // 企业银行账号
|
||||||
|
private String accountingDate; // 账号日期
|
||||||
|
private String trxDate; // 交易日期
|
||||||
|
private String currency; // 币种
|
||||||
|
private String cashType; // 交易类型
|
||||||
|
private String trxFlag; // 交易标志位
|
||||||
|
private String exceptionType; // 异常类型
|
||||||
|
private String customerAccountName;// 对手方企业名称
|
||||||
|
private String customerAccountNo; // 对手方账号
|
||||||
|
private String customerBank; // 对手方银行
|
||||||
|
private String customerReference; // 对手方备注
|
||||||
|
private String userMemo; // 用户交易摘要
|
||||||
|
private String bankComments; // 银行交易摘要
|
||||||
|
private String bankTrxNumber; // 银行交易号
|
||||||
|
private String bank; // 所属银行缩写
|
||||||
|
private String metaJson; // meta json
|
||||||
|
private String paymentMethod; // 交易方式
|
||||||
|
private String cretNo; // 身份证号
|
||||||
|
|
||||||
|
// 日期类型
|
||||||
|
private Date createDate; // 创建时间
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、转换方法设计
|
||||||
|
|
||||||
|
### 4.1 方法签名
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 从流水分析接口响应转换为实体
|
||||||
|
*
|
||||||
|
* @param item 流水分析接口返回的流水项
|
||||||
|
* @return 流水实体,如果 item 为 null 则返回 null
|
||||||
|
*/
|
||||||
|
public static CcdiBankStatement fromResponse(BankStatementItem item)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 转换逻辑
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static CcdiBankStatement fromResponse(BankStatementItem item) {
|
||||||
|
// 1. 空值检查
|
||||||
|
if (item == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// 6. 注意:project_id 需要在 Service 层设置
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 BeanUtils 行为说明
|
||||||
|
|
||||||
|
| 场景 | BeanUtils 行为 |
|
||||||
|
|------|---------------|
|
||||||
|
| 字段名相同且类型兼容 | 自动复制 |
|
||||||
|
| 字段名相同但类型不兼容 | 抛出异常 |
|
||||||
|
| 源对象中不存在目标字段 | 忽略,不抛异常 |
|
||||||
|
| 目标对象中不存在源字段 | 忽略,不抛异常 |
|
||||||
|
| 源字段为 null | 复制 null 值到目标字段 |
|
||||||
|
|
||||||
|
**注意事项:**
|
||||||
|
- BeanUtils 会忽略响应对象中额外的字段(如 `transAmount`, `attachments` 等)
|
||||||
|
- 需要手动处理字段名不一致的3个字段
|
||||||
|
- `meta_json` 字段强制设为 null
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、使用示例
|
||||||
|
|
||||||
|
### 5.1 Service层调用
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class BankStatementServiceImpl implements IBankStatementService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiBankStatementMapper bankStatementMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LsfxAnalysisClient lsfxClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取并保存流水数据
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param request 查询请求
|
||||||
|
* @return 保存的记录数
|
||||||
|
*/
|
||||||
|
public int fetchAndSaveBankStatements(Long projectId, GetBankStatementRequest request) {
|
||||||
|
// 1. 调用流水分析接口
|
||||||
|
GetBankStatementResponse response = lsfxClient.getBankStatement(request);
|
||||||
|
|
||||||
|
// 2. 校验响应
|
||||||
|
if (response == null || !Boolean.TRUE.equals(response.getSuccessResponse())) {
|
||||||
|
throw new ServiceException("获取流水数据失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BankStatementItem> items = response.getData().getBankStatementList();
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 转换并设置项目ID
|
||||||
|
List<CcdiBankStatement> entities = items.stream()
|
||||||
|
.map(item -> {
|
||||||
|
CcdiBankStatement entity = CcdiBankStatement.fromResponse(item);
|
||||||
|
if (entity != null) {
|
||||||
|
entity.setProjectId(projectId); // 设置关联项目ID
|
||||||
|
}
|
||||||
|
return entity;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 4. 批量插入数据库
|
||||||
|
return bankStatementMapper.insertBatch(entities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 单条数据转换
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 从接口响应转换单条流水
|
||||||
|
BankStatementItem item = response.getData().getBankStatementList().get(0);
|
||||||
|
CcdiBankStatement entity = CcdiBankStatement.fromResponse(item);
|
||||||
|
|
||||||
|
// 设置业务字段
|
||||||
|
entity.setProjectId(1001L);
|
||||||
|
|
||||||
|
// 保存到数据库
|
||||||
|
bankStatementMapper.insert(entity);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 批量数据转换
|
||||||
|
|
||||||
|
```java
|
||||||
|
List<CcdiBankStatement> entities = response.getData().getBankStatementList()
|
||||||
|
.stream()
|
||||||
|
.map(CcdiBankStatement::fromResponse)
|
||||||
|
.peek(entity -> entity.setProjectId(projectId))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
bankStatementMapper.insertBatch(entities);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、错误处理
|
||||||
|
|
||||||
|
### 6.1 空指针异常防护
|
||||||
|
|
||||||
|
**问题:** 接口响应可能为 null 或数据列表为空
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
```java
|
||||||
|
// 在 fromResponse 方法中
|
||||||
|
if (item == null) {
|
||||||
|
log.warn("流水项为空,无法转换");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在 Service 层
|
||||||
|
if (response == null || !Boolean.TRUE.equals(response.getSuccessResponse())) {
|
||||||
|
throw new ServiceException("获取流水数据失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BankStatementItem> items = response.getData().getBankStatementList();
|
||||||
|
if (items == null || items.isEmpty()) {
|
||||||
|
return 0; // 正常返回,不是异常情况
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 类型转换异常
|
||||||
|
|
||||||
|
**问题:** BeanUtils 在字段类型不匹配时会抛出异常
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
1. 确保 `BankStatementItem` 和 `CcdiBankStatement` 字段类型一致
|
||||||
|
2. BigDecimal、Integer、Long 类型已验证兼容
|
||||||
|
3. 添加异常捕获日志:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public static CcdiBankStatement fromResponse(BankStatementItem item) {
|
||||||
|
if (item == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
CcdiBankStatement entity = new CcdiBankStatement();
|
||||||
|
BeanUtils.copyProperties(item, entity);
|
||||||
|
entity.setLeAccountNo(item.getAccountMaskNo());
|
||||||
|
entity.setCustomerAccountNo(item.getCustomerAccountMaskNo());
|
||||||
|
entity.setBatchSequence(item.getUploadSequnceNumber());
|
||||||
|
entity.setMetaJson(null);
|
||||||
|
return entity;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("流水数据转换失败, bankStatementId={}", item.getBankStatementId(), e);
|
||||||
|
throw new RuntimeException("流水数据转换失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 数据验证
|
||||||
|
|
||||||
|
**必填字段验证:**
|
||||||
|
```java
|
||||||
|
// 在 Service 层验证业务字段
|
||||||
|
if (entity.getProjectId() == null) {
|
||||||
|
throw new IllegalArgumentException("项目ID不能为空");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据库约束:**
|
||||||
|
- `bank_statement_id` 自增主键,无需验证
|
||||||
|
- 其他字段根据业务需求设置数据库约束(NOT NULL、DEFAULT等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、性能考虑
|
||||||
|
|
||||||
|
### 7.1 BeanUtils 性能
|
||||||
|
|
||||||
|
**特点:**
|
||||||
|
- 使用 Java 反射机制
|
||||||
|
- 单次转换性能影响可忽略(< 1ms)
|
||||||
|
- 批量转换时累计开销需要考虑
|
||||||
|
|
||||||
|
**性能数据(参考):**
|
||||||
|
| 操作 | 耗时 |
|
||||||
|
|------|------|
|
||||||
|
| 单次 BeanUtils.copyProperties() | < 1ms |
|
||||||
|
| 100次转换 | ~50ms |
|
||||||
|
| 1000次转换 | ~200ms |
|
||||||
|
|
||||||
|
**优化建议:**
|
||||||
|
- 对于单次或小批量转换(<100条),直接使用 BeanUtils
|
||||||
|
- 对于大批量转换(>1000条),可考虑:
|
||||||
|
1. 使用 MapStruct(编译期生成代码,无反射)
|
||||||
|
2. 异步批量处理
|
||||||
|
3. 分批插入数据库
|
||||||
|
|
||||||
|
### 7.2 数据库批量插入
|
||||||
|
|
||||||
|
**推荐方式:**
|
||||||
|
```java
|
||||||
|
// MyBatis Plus 批量插入
|
||||||
|
@Service
|
||||||
|
public class BankStatementServiceImpl {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiBankStatementMapper bankStatementMapper;
|
||||||
|
|
||||||
|
public int insertBatch(List<CcdiBankStatement> entities) {
|
||||||
|
if (entities == null || entities.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分批插入,每批 1000 条
|
||||||
|
int batchSize = 1000;
|
||||||
|
int totalInserted = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < entities.size(); i += batchSize) {
|
||||||
|
int end = Math.min(i + batchSize, entities.size());
|
||||||
|
List<CcdiBankStatement> batch = entities.subList(i, end);
|
||||||
|
bankStatementMapper.insertBatch(batch);
|
||||||
|
totalInserted += batch.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalInserted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 内存考虑
|
||||||
|
|
||||||
|
**对象占用空间估算:**
|
||||||
|
- 单个 `CcdiBankStatement` 对象约 1KB(包含所有字段)
|
||||||
|
- 1000条流水数据约占用 1MB 内存
|
||||||
|
- 10000条流水数据约占用 10MB 内存
|
||||||
|
|
||||||
|
**建议:**
|
||||||
|
- 对于超大数据量(>10000条),使用流式处理:
|
||||||
|
```java
|
||||||
|
response.getData().getBankStatementList()
|
||||||
|
.stream()
|
||||||
|
.map(CcdiBankStatement::fromResponse)
|
||||||
|
.forEach(entity -> {
|
||||||
|
// 立即处理,不保留在内存中
|
||||||
|
bankStatementMapper.insert(entity);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、测试策略
|
||||||
|
|
||||||
|
### 8.1 单元测试
|
||||||
|
|
||||||
|
**测试类:** `CcdiBankStatementTest`
|
||||||
|
|
||||||
|
**测试用例:**
|
||||||
|
|
||||||
|
| 测试场景 | 测试方法 | 验证点 |
|
||||||
|
|---------|---------|--------|
|
||||||
|
| 正常转换 | `testFromResponse_Success` | 所有字段正确映射 |
|
||||||
|
| 空值处理 | `testFromResponse_Null` | 返回 null |
|
||||||
|
| 字段名映射 | `testFromResponse_FieldMapping` | 3个特殊字段正确映射 |
|
||||||
|
| meta_json | `testFromResponse_MetaJson` | 强制为 null |
|
||||||
|
|
||||||
|
**测试代码示例:**
|
||||||
|
```java
|
||||||
|
@Test
|
||||||
|
public void testFromResponse_Success() {
|
||||||
|
// 准备测试数据
|
||||||
|
BankStatementItem item = new BankStatementItem();
|
||||||
|
item.setBankStatementId(123456L);
|
||||||
|
item.setLeId(100);
|
||||||
|
item.setAccountMaskNo("6222****1234");
|
||||||
|
item.setDrAmount(new BigDecimal("1000.00"));
|
||||||
|
|
||||||
|
// 执行转换
|
||||||
|
CcdiBankStatement entity = CcdiBankStatement.fromResponse(item);
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertNotNull(entity);
|
||||||
|
assertEquals(123456L, entity.getBankStatementId());
|
||||||
|
assertEquals(100, entity.getLeId());
|
||||||
|
assertEquals("6222****1234", entity.getLeAccountNo());
|
||||||
|
assertEquals(new BigDecimal("1000.00"), entity.getAmountDr());
|
||||||
|
assertNull(entity.getMetaJson());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 集成测试
|
||||||
|
|
||||||
|
**测试场景:**
|
||||||
|
1. 完整流程:调用接口 → 转换数据 → 保存数据库
|
||||||
|
2. 数据库查询:验证字段值正确性
|
||||||
|
3. 关联查询:验证 `project_id` 关联有效
|
||||||
|
|
||||||
|
### 8.3 性能测试
|
||||||
|
|
||||||
|
**测试指标:**
|
||||||
|
- 单次转换耗时
|
||||||
|
- 1000次批量转换耗时
|
||||||
|
- 数据库批量插入耗时
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、部署检查清单
|
||||||
|
|
||||||
|
### 9.1 数据库修改
|
||||||
|
|
||||||
|
- [ ] 执行 ALTER TABLE 添加 `project_id` 字段
|
||||||
|
- [ ] 创建索引 `idx_project_id`
|
||||||
|
- [ ] 验证字段类型和长度
|
||||||
|
|
||||||
|
### 9.2 代码检查
|
||||||
|
|
||||||
|
- [ ] `ccdi-project` 模块已依赖 `ccdi-lsfx`
|
||||||
|
- [ ] 实体类字段类型与数据库一致
|
||||||
|
- [ ] 转换方法处理所有特殊字段
|
||||||
|
- [ ] Service 层正确设置 `project_id`
|
||||||
|
|
||||||
|
### 9.3 测试验证
|
||||||
|
|
||||||
|
- [ ] 单元测试通过
|
||||||
|
- [ ] 集成测试通过
|
||||||
|
- [ ] 性能测试达标
|
||||||
|
|
||||||
|
### 9.4 文档更新
|
||||||
|
|
||||||
|
- [ ] 更新 CLAUDE.md 文档
|
||||||
|
- [ ] 更新数据库设计文档
|
||||||
|
- [ ] 添加 API 文档说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、附录
|
||||||
|
|
||||||
|
### 10.1 完整字段映射表
|
||||||
|
|
||||||
|
| 序号 | 数据库字段 | Java字段 | Java类型 | 响应字段 | 说明 |
|
||||||
|
|------|-----------|---------|---------|---------|------|
|
||||||
|
| 1 | bank_statement_id | bankStatementId | Long | bankStatementId | 主键自增 |
|
||||||
|
| 2 | project_id | projectId | Long | - | **新增字段** |
|
||||||
|
| 3 | LE_ID | leId | Integer | leId | 企业ID |
|
||||||
|
| 4 | ACCOUNT_ID | accountId | Long | accountId | 账号ID |
|
||||||
|
| 5 | LE_ACCOUNT_NAME | leAccountName | String | leName | 企业账号名称 |
|
||||||
|
| 6 | LE_ACCOUNT_NO | leAccountNo | String | accountMaskNo | **手动映射** |
|
||||||
|
| 7 | ACCOUNTING_DATE_ID | accountingDateId | Integer | accountingDateId | 账号日期ID |
|
||||||
|
| 8 | ACCOUNTING_DATE | accountingDate | String | accountingDate | 账号日期 |
|
||||||
|
| 9 | TRX_DATE | trxDate | String | trxDate | 交易日期 |
|
||||||
|
| 10 | CURRENCY | currency | String | currency | 币种 |
|
||||||
|
| 11 | AMOUNT_DR | amountDr | BigDecimal | drAmount | 付款金额 |
|
||||||
|
| 12 | AMOUNT_CR | amountCr | BigDecimal | crAmount | 收款金额 |
|
||||||
|
| 13 | AMOUNT_BALANCE | amountBalance | BigDecimal | balanceAmount | 余额 |
|
||||||
|
| 14 | CASH_TYPE | cashType | String | cashType | 交易类型 |
|
||||||
|
| 15 | CUSTOMER_LE_ID | customerLeId | Integer | customerId | 对手方企业ID |
|
||||||
|
| 16 | CUSTOMER_ACCOUNT_NAME | customerAccountName | String | customerName | 对手方企业名称 |
|
||||||
|
| 17 | CUSTOMER_ACCOUNT_NO | customerAccountNo | String | customerAccountMaskNo | **手动映射** |
|
||||||
|
| 18 | customer_bank | customerBank | String | customerBank | 对手方银行 |
|
||||||
|
| 19 | customer_reference | customerReference | String | customerReference | 对手方备注 |
|
||||||
|
| 20 | USER_MEMO | userMemo | String | userMemo | 用户交易摘要 |
|
||||||
|
| 21 | BANK_COMMENTS | bankComments | String | bankComments | 银行交易摘要 |
|
||||||
|
| 22 | BANK_TRX_NUMBER | bankTrxNumber | String | bankTrxNumber | 银行交易号 |
|
||||||
|
| 23 | BANK | bank | String | bank | 所属银行缩写 |
|
||||||
|
| 24 | TRX_FLAG | trxFlag | String | transFlag | 交易标志位 |
|
||||||
|
| 25 | TRX_TYPE | trxType | Integer | transTypeId | 分类ID |
|
||||||
|
| 26 | EXCEPTION_TYPE | exceptionType | String | exceptionType | 异常类型 |
|
||||||
|
| 27 | internal_flag | internalFlag | Integer | internalFlag | 是否为内部交易 |
|
||||||
|
| 28 | batch_id | batchId | Integer | batchId | 上传logId |
|
||||||
|
| 29 | batch_sequence | batchSequence | Integer | uploadSequnceNumber | **手动映射** |
|
||||||
|
| 30 | CREATE_DATE | createDate | Date | createDate | 创建时间 |
|
||||||
|
| 31 | created_by | createdBy | Long | createdBy | 创建者 |
|
||||||
|
| 32 | meta_json | metaJson | String | - | **强制null** |
|
||||||
|
| 33 | no_balance | noBalance | Integer | isNoBalance | 是否包含余额 |
|
||||||
|
| 34 | begin_balance | beginBalance | Integer | isBeginBalance | 初始余额 |
|
||||||
|
| 35 | end_balance | endBalance | Integer | isEndBalance | 结束余额 |
|
||||||
|
| 36 | override_bs_id | overrideBsId | Long | overrideBsId | 覆盖标识 |
|
||||||
|
| 37 | payment_method | paymentMethod | String | paymentMethod | 交易方式 |
|
||||||
|
| 38 | cret_no | cretNo | String | cretNo | 身份证号 |
|
||||||
|
| 39 | group_id | groupId | Integer | groupId | 项目id |
|
||||||
|
|
||||||
|
### 10.2 参考文档
|
||||||
|
|
||||||
|
- [ccdi_bank_statement.md](../../assets/对接流水分析/ccdi_bank_statement.md)
|
||||||
|
- [MyBatis Plus 官方文档](https://baomidou.com/)
|
||||||
|
- [Spring BeanUtils 文档](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本:** 1.0
|
||||||
|
**最后更新:** 2026-03-04
|
||||||
745
docs/plans/2026-03-04-bank-statement-implementation.md
Normal file
745
docs/plans/2026-03-04-bank-statement-implementation.md
Normal file
@@ -0,0 +1,745 @@
|
|||||||
|
# 银行流水实体类实现计划
|
||||||
|
|
||||||
|
> **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
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
如果没有,添加上述依赖。
|
||||||
|
|
||||||
|
**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<CcdiBankStatement> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量插入银行流水
|
||||||
|
*
|
||||||
|
* @param list 银行流水列表
|
||||||
|
* @return 插入记录数
|
||||||
|
*/
|
||||||
|
int insertBatch(@Param("list") List<CcdiBankStatement> 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
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper">
|
||||||
|
|
||||||
|
<resultMap type="com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement" id="CcdiBankStatementResult">
|
||||||
|
<id property="bankStatementId" column="bank_statement_id" />
|
||||||
|
<result property="projectId" column="project_id" />
|
||||||
|
<result property="leId" column="LE_ID" />
|
||||||
|
<result property="accountId" column="ACCOUNT_ID" />
|
||||||
|
<result property="groupId" column="group_id" />
|
||||||
|
<result property="leAccountName" column="LE_ACCOUNT_NAME" />
|
||||||
|
<result property="leAccountNo" column="LE_ACCOUNT_NO" />
|
||||||
|
<result property="accountingDateId" column="ACCOUNTING_DATE_ID" />
|
||||||
|
<result property="accountingDate" column="ACCOUNTING_DATE" />
|
||||||
|
<result property="trxDate" column="TRX_DATE" />
|
||||||
|
<result property="currency" column="CURRENCY" />
|
||||||
|
<result property="amountDr" column="AMOUNT_DR" />
|
||||||
|
<result property="amountCr" column="AMOUNT_CR" />
|
||||||
|
<result property="amountBalance" column="AMOUNT_BALANCE" />
|
||||||
|
<result property="cashType" column="CASH_TYPE" />
|
||||||
|
<result property="customerLeId" column="CUSTOMER_LE_ID" />
|
||||||
|
<result property="customerAccountName" column="CUSTOMER_ACCOUNT_NAME" />
|
||||||
|
<result property="customerAccountNo" column="CUSTOMER_ACCOUNT_NO" />
|
||||||
|
<result property="customerBank" column="customer_bank" />
|
||||||
|
<result property="customerReference" column="customer_reference" />
|
||||||
|
<result property="userMemo" column="USER_MEMO" />
|
||||||
|
<result property="bankComments" column="BANK_COMMENTS" />
|
||||||
|
<result property="bankTrxNumber" column="BANK_TRX_NUMBER" />
|
||||||
|
<result property="bank" column="BANK" />
|
||||||
|
<result property="trxFlag" column="TRX_FLAG" />
|
||||||
|
<result property="trxType" column="TRX_TYPE" />
|
||||||
|
<result property="exceptionType" column="EXCEPTION_TYPE" />
|
||||||
|
<result property="internalFlag" column="internal_flag" />
|
||||||
|
<result property="batchId" column="batch_id" />
|
||||||
|
<result property="batchSequence" column="batch_sequence" />
|
||||||
|
<result property="createDate" column="CREATE_DATE" />
|
||||||
|
<result property="createdBy" column="created_by" />
|
||||||
|
<result property="metaJson" column="meta_json" />
|
||||||
|
<result property="noBalance" column="no_balance" />
|
||||||
|
<result property="beginBalance" column="begin_balance" />
|
||||||
|
<result property="endBalance" column="end_balance" />
|
||||||
|
<result property="overrideBsId" column="override_bs_id" />
|
||||||
|
<result property="paymentMethod" column="payment_method" />
|
||||||
|
<result property="cretNo" column="cret_no" />
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="selectCcdiBankStatementVo">
|
||||||
|
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
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<insert id="insertBatch" parameterType="java.util.List">
|
||||||
|
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
|
||||||
|
<foreach collection="list" item="item" separator=",">
|
||||||
|
(
|
||||||
|
#{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}
|
||||||
|
)
|
||||||
|
</foreach>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
```
|
||||||
|
|
||||||
|
**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
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ruoyi</groupId>
|
||||||
|
<artifactId>ccdi-lsfx</artifactId>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
如果没有,添加上述依赖并重新编译。
|
||||||
|
|
||||||
|
**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
|
||||||
1713
docs/plans/2026-03-04-lsfx-interface-update-plan.md
Normal file
1713
docs/plans/2026-03-04-lsfx-interface-update-plan.md
Normal file
File diff suppressed because it is too large
Load Diff
14
sql/ccdi_bank_statement_add_project_id.sql
Normal file
14
sql/ccdi_bank_statement_add_project_id.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- 为 ccdi_bank_statement 表添加 project_id 字段
|
||||||
|
-- 用途:关联项目ID,实现流水数据与项目的业务关联
|
||||||
|
-- 作者:系统自动生成
|
||||||
|
-- 日期:2026-03-04
|
||||||
|
|
||||||
|
USE ccdi;
|
||||||
|
|
||||||
|
-- 添加 project_id 字段
|
||||||
|
ALTER TABLE `ccdi_bank_statement`
|
||||||
|
ADD COLUMN `project_id` bigint(20) DEFAULT NULL COMMENT '关联项目ID' AFTER `bank_statement_id`;
|
||||||
|
|
||||||
|
-- 添加索引以提升查询性能
|
||||||
|
ALTER TABLE `ccdi_bank_statement`
|
||||||
|
ADD INDEX `idx_project_id` (`project_id`);
|
||||||
Reference in New Issue
Block a user