diff --git a/assets/对接流水分析/ccdi_bank_statement.md b/assets/对接流水分析/ccdi_bank_statement.md new file mode 100644 index 0000000..a27108c --- /dev/null +++ b/assets/对接流水分析/ccdi_bank_statement.md @@ -0,0 +1,92 @@ +# 兰溪存储的流水表的表结构 + +```sql +CREATE TABLE `ccdi_bank_statement` ( + `bank_statement_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `LE_ID` int(10) unsigned DEFAULT '0' COMMENT '企业ID', + `ACCOUNT_ID` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '账号ID', + `LE_ACCOUNT_NAME` varchar(240) DEFAULT 'NONE' COMMENT '企业账号名称', + `LE_ACCOUNT_NO` varchar(240) DEFAULT NULL COMMENT '企业银行账号', + `ACCOUNTING_DATE_ID` int(11) DEFAULT NULL COMMENT '账号日期ID', + `ACCOUNTING_DATE` varchar(10) DEFAULT '0000-00-00' COMMENT '账号日期', + `TRX_DATE` varchar(20) NOT NULL COMMENT '交易日期', + `CURRENCY` varchar(10) DEFAULT NULL COMMENT '币种', + `AMOUNT_DR` decimal(19,2) NOT NULL DEFAULT '0.00' COMMENT '付款金额', + `AMOUNT_CR` decimal(19,2) NOT NULL DEFAULT '0.00' COMMENT '收款金额', + `AMOUNT_BALANCE` decimal(19,2) NOT NULL COMMENT '余额', + `CASH_TYPE` varchar(500) DEFAULT NULL COMMENT '交易类型', + `CUSTOMER_LE_ID` int(11) DEFAULT '-1' COMMENT '对手方企业ID', + `CUSTOMER_ACCOUNT_NAME` varchar(240) DEFAULT NULL COMMENT '对手方企业名称', + `CUSTOMER_ACCOUNT_NO` varchar(240) DEFAULT NULL COMMENT '对手方账号', + `customer_bank` varchar(300) DEFAULT NULL COMMENT '对手方银行', + `customer_reference` varchar(500) DEFAULT NULL COMMENT '对手方备注', + `USER_MEMO` varchar(1000) DEFAULT NULL COMMENT '用户交易摘要', + `BANK_COMMENTS` varchar(240) DEFAULT NULL COMMENT '银行交易摘要', + `BANK_TRX_NUMBER` varchar(240) DEFAULT NULL COMMENT '银行交易号', + `BANK` varchar(250) NOT NULL DEFAULT '' COMMENT '所属银行缩写', + `TRX_FLAG` varchar(2) DEFAULT '0' COMMENT '交易标志位', + `TRX_TYPE` int(11) NOT NULL DEFAULT '0' COMMENT '分类ID', + `EXCEPTION_TYPE` varchar(50) NOT NULL DEFAULT '' COMMENT '异常类型', + `internal_flag` tinyint(1) DEFAULT '0' COMMENT '"是否为内部交易1 是 0 否"', + `batch_id` int(11) NOT NULL DEFAULT '0' COMMENT '上传logId对应upload_log', + `batch_sequence` int(11) NOT NULL COMMENT '每次上传在文件中的line', + `CREATE_DATE` datetime DEFAULT NULL COMMENT '创建时间内', + `created_by` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '创建者', + `meta_json` text COMMENT '"meta json"', + `no_balance` tinyint(1) DEFAULT '0' COMMENT '是否包含余额', + `begin_balance` tinyint(1) DEFAULT '0' COMMENT '初始余额', + `end_balance` tinyint(1) DEFAULT '0' COMMENT '结束余额', + `group_id` int(11) DEFAULT '0' COMMENT '项目id', + `override_bs_id` bigint(20) DEFAULT '0' COMMENT '=0表示该数据未覆盖主表,>0表示覆盖主表,<0表示被主表覆盖', + `payment_method` varchar(500) DEFAULT NULL COMMENT '微信、支付宝流水字段,交易方式', + `cret_no` varchar(20) COMMENT '身份证号', + PRIMARY KEY (`bank_statement_id`), + KEY `idx_batch_id_account` (`batch_id`,`LE_ACCOUNT_NO`,`ACCOUNTING_DATE_ID`), + KEY `GROUP_ID` (`group_id`), + KEY `c4c_bank_statement_stg_batch_id_IDX` (`batch_id`,`LE_ACCOUNT_NO`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='银行流水的中间处理表'; +``` + +流水表和返回值的对应关系 + +| 序号 | ccdi_bank_statement | 返回值 | +| --- | --- | --- | +| 1 | bank_statement_id | bankStatementId | +| 2 | LE_ID | leId | +| 3 | ACCOUNT_ID | accountId | +| 4 | LE_ACCOUNT_NAME | leName | +| 5 | LE_ACCOUNT_NO | accountNo | +| 6 | ACCOUNTING_DATE_ID | accountingDateId | +| 7 | ACCOUNTING_DATE | accountingDate | +| 8 | TRX_DATE | trxDate | +| 9 | CURRENCY | currency | +| 10 | AMOUNT_DR | drAmount | +| 11 | AMOUNT_CR | crAmount | +| 12 | AMOUNT_BALANCE | balanceAmount | +| 13 | CASH_TYPE | cashType | +| 14 | CUSTOMER_LE_ID | customerId | +| 15 | CUSTOMER_ACCOUNT_NAME | customerName | +| 16 | CUSTOMER_ACCOUNT_NO | customerAccountNo | +| 17 | customer_bank | customerBank | +| 18 | customer_reference | customerReference | +| 19 | USER_MEMO | userMemo | +| 20 | BANK_COMMENTS | bankComments | +| 21 | BANK_TRX_NUMBER | bankTrxNumber | +| 22 | BANK | bank | +| 23 | TRX_FLAG | transFlag | +| 24 | TRX_TYPE | transTypeId | +| 25 | EXCEPTION_TYPE | exceptionType | +| 26 | internal_flag | internalFlag | +| 27 | batch_id | batchId | +| 28 | batch_sequence | uploadSequnceNumber | +| 29 | CREATE_DATE | createDate | +| 30 | created_by | createdBy | +| 31 | meta_json | 设置为null | +| 32 | no_balance | isNoBalance | +| 33 | begin_balance | isBeginBalance | +| 34 | end_balance | isEndBalance | +| 35 | override_bs_id | overrideBsId | +| 36 | payment_method | paymentMethod | +| 37 | cret_no | cretNo | +| 38 | group_id | groupId | + diff --git a/assets/对接流水分析/~$-流水分析对接_new.docx b/assets/对接流水分析/~$-流水分析对接_new.docx new file mode 100644 index 0000000..8a890f5 Binary files /dev/null and b/assets/对接流水分析/~$-流水分析对接_new.docx differ diff --git a/assets/对接流水分析/兰溪-流水分析对接_new.docx b/assets/对接流水分析/兰溪-流水分析对接_new.docx deleted file mode 100644 index 04a0afd..0000000 Binary files a/assets/对接流水分析/兰溪-流水分析对接_new.docx and /dev/null differ diff --git a/doc/design/2026-03-04-add-lsfx-project-id.sql b/doc/design/2026-03-04-add-lsfx-project-id.sql new file mode 100644 index 0000000..1a541da --- /dev/null +++ b/doc/design/2026-03-04-add-lsfx-project-id.sql @@ -0,0 +1,30 @@ +-- ==================================== +-- 功能:ccdi_project 表新增流水分析平台项目ID字段 +-- 日期:2026-03-04 +-- 作者:Claude Code +-- 说明:为支持创建项目时集成流水分析平台,需要新增字段存储流水分析平台返回的projectId +-- ==================================== + +USE ccdi; + +-- 新增字段 +ALTER TABLE `ccdi_project` +ADD COLUMN `lsfx_project_id` INT(11) DEFAULT NULL COMMENT '流水分析平台项目ID' +AFTER `low_risk_count`; + +-- 验证字段是否添加成功 +SELECT + COLUMN_NAME, + COLUMN_TYPE, + IS_NULLABLE, + COLUMN_DEFAULT, + COLUMN_COMMENT +FROM + INFORMATION_SCHEMA.COLUMNS +WHERE + TABLE_SCHEMA = 'ccdi' + AND TABLE_NAME = 'ccdi_project' + AND COLUMN_NAME = 'lsfx_project_id'; + +-- 提示信息 +SELECT '字段 lsfx_project_id 添加成功!' AS message; diff --git a/doc/design/2026-03-04-create-project-integrate-lsfx-design.md b/doc/design/2026-03-04-create-project-integrate-lsfx-design.md new file mode 100644 index 0000000..197af96 --- /dev/null +++ b/doc/design/2026-03-04-create-project-integrate-lsfx-design.md @@ -0,0 +1,552 @@ +# 创建项目时集成流水分析平台设计方案 + +**文档版本**: 1.0 +**创建日期**: 2026-03-04 +**作者**: Claude Code +**状态**: 待实施 + +--- + +## 1. 需求概述 + +### 1.1 背景 + +在纪检初核系统中,创建项目时需要同步在流水分析平台创建对应的项目,以便后续进行流水分析操作。 + +### 1.2 目标 + +- 创建项目时自动调用流水分析平台的 `getToken` 接口 +- 获取返回的 `projectId` 并保存到项目表 +- 确保数据一致性,调用失败时项目创建也失败 + +### 1.3 参考文档 + +- 《兰溪-流水分析对接-新版.md》 +- 项目现有代码:`ccdi-project` 模块、`ccdi-lsfx` 模块 + +--- + +## 2. 设计方案 + +### 2.1 总体架构 + +#### 业务流程 + +``` +用户创建项目 + ↓ +Controller接收请求 + ↓ +Service层处理 + ├─→ 生成projectNo (902000_时间戳) + ├─→ 调用流水分析平台getToken接口 + │ ├─→ 成功:获取projectId + │ └─→ 失败:抛出异常,事务回滚 + ├─→ 保存项目(包含projectId) + └─→ 返回项目信息 +``` + +#### 技术方案 + +- **调用方式**: 同步调用 +- **集成位置**: Service层(`CcdiProjectServiceImpl`) +- **事务管理**: Spring声明式事务,失败自动回滚 +- **异常处理**: 依赖 `LsfxAnalysisClient` 的异常处理 + +### 2.2 核心设计决策 + +| 决策点 | 选择 | 理由 | +|-------|------|------| +| 调用方式 | 同步调用 | 数据一致性强,用户体验清晰 | +| 保存策略 | 仅保存projectId | token 使用一次后失效,无需保存 | +| 集成位置 | Service层 | 业务逻辑集中,事务管理简单 | +| 异常处理 | 依赖客户端 | 避免重复日志,Service层只做业务校验 | + +--- + +## 3. 详细设计 + +### 3.1 数据库设计 + +#### ccdi_project 表新增字段 + +```sql +ALTER TABLE `ccdi_project` +ADD COLUMN `lsfx_project_id` INT(11) DEFAULT NULL COMMENT '流水分析平台项目ID' +AFTER `low_risk_count`; +``` + +**字段说明:** +- 字段名: `lsfx_project_id` +- 类型: `INT(11)`(与流水分析平台保持一致) +- 允许为空: 是(理论上不会为空,因为失败会回滚) +- 索引: 不设置唯一索引(流水分析平台的 projectId 可能重复) + +### 3.2 参数映射规则 + +根据《兰溪-流水分析对接-新版.md》文档,GetTokenRequest 参数配置如下: + +#### 必填参数(固定值) + +| 参数名 | 值 | 说明 | +|-------|-----|------| +| `userId` | `"902001"` | 操作人员编号(固定值) | +| `userName` | `"902001"` | 操作人员姓名(固定值) | +| `role` | `"VIEWER"` | 人员角色(固定值) | +| `orgCode` | `"902000"` | 行社机构号(固定值) | +| `analysisType` | `"-1"` | 分析类型(固定值) | +| `departmentCode` | `"902000"` | 客户经理所属营业部机构编码(固定值) | + +#### 必填参数(动态生成) + +| 参数名 | 生成规则 | 说明 | +|-------|---------|------| +| `projectNo` | `"902000_" + System.currentTimeMillis()` | 项目编号 | +| `entityName` | 使用前端传入的 `projectName` | 项目名称 | + +#### 自动生成参数 + +| 参数名 | 生成位置 | 说明 | +|-------|---------|------| +| `appId` | `LsfxAnalysisClient` 配置 | 固定值:`remote_app` | +| `appSecretCode` | `LsfxAnalysisClient.getToken()` | MD5(projectNo + "_" + entityName + "_" + appSecret) | + +#### 可选参数 + +以下参数不传递(暂不需要): +- `entityId` (企业统信码或个人身份证号) +- `xdRelatedPersons` (信贷关联人信息) +- `jzDataDateId` (金综链流水日期ID) +- `innerBSStartDateId` (行内流水开始日期) +- `innerBSEndDateId` (行内流水结束日期) + +### 3.3 代码实现 + +#### 3.3.1 实体类修改 + +**CcdiProject.java** + +```java +@Data +@TableName("ccdi_project") +public class CcdiProject implements Serializable { + + // ... 现有字段 ... + + /** 低风险人数 */ + private Integer lowRiskCount; + + /** 流水分析平台项目ID */ + private Integer lsfxProjectId; // 新增字段 + + /** 删除标志 */ + @TableLogic + private String delFlag; + + // ... 审计字段 ... +} +``` + +**CcdiProjectVO.java** + +```java +@Data +public class CcdiProjectVO implements Serializable { + + // ... 现有字段 ... + + /** 低风险人数 */ + private Integer lowRiskCount; + + /** 流水分析平台项目ID */ + private Integer lsfxProjectId; // 新增字段 + + /** 创建时间 */ + private Date createTime; + + // ... 其他字段 ... +} +``` + +#### 3.3.2 Service实现 + +**CcdiProjectServiceImpl.java** + +```java +@Service +public class CcdiProjectServiceImpl implements ICcdiProjectService { + + @Resource + private CcdiProjectMapper projectMapper; + + @Resource + private LsfxAnalysisClient lsfxAnalysisClient; // 新增依赖注入 + + @Override + @Transactional(rollbackFor = Exception.class) + public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) { + // 1. 调用流水分析平台获取projectId + Integer lsfxProjectId = callLsfxPlatform(dto.getProjectName()); + + // 2. 创建项目实体 + CcdiProject project = new CcdiProject(); + BeanUtils.copyProperties(dto, project); + + // 3. 设置默认值和流水分析平台ID + project.setStatus("0"); // 进行中 + project.setIsArchived(0); // 未归档 + project.setTargetCount(0); + project.setHighRiskCount(0); + project.setMediumRiskCount(0); + project.setLowRiskCount(0); + project.setLsfxProjectId(lsfxProjectId); // 设置流水分析平台ID + + // 4. 保存到数据库 + projectMapper.insert(project); + + // 5. 返回VO + CcdiProjectVO vo = new CcdiProjectVO(); + BeanUtils.copyProperties(project, vo); + return vo; + } + + /** + * 调用流水分析平台获取projectId + * + * @param projectName 项目名称 + * @return 流水分析平台项目ID + * @throws ServiceException 调用失败或响应无效时抛出 + */ + private Integer callLsfxPlatform(String projectName) { + // 构建请求参数 + GetTokenRequest request = new GetTokenRequest(); + request.setProjectNo("902000_" + System.currentTimeMillis()); + request.setEntityName(projectName); + request.setUserId("902001"); + request.setUserName("902001"); + request.setRole("VIEWER"); + request.setOrgCode("902000"); + request.setAnalysisType("-1"); + request.setDepartmentCode("902000"); + + // 调用流水分析平台(异常处理和日志已在 LsfxAnalysisClient 中完成) + GetTokenResponse response = lsfxAnalysisClient.getToken(request); + + // 业务层校验:确保响应有效 + if (response == null || response.getData() == null) { + throw new ServiceException("流水分析平台响应数据为空"); + } + + if (response.getData().getProjectId() == null) { + throw new ServiceException("流水分析平台返回的projectId为空"); + } + + // 校验返回码 + if (!"200".equals(response.getCode())) { + throw new ServiceException("流水分析平台返回错误: " + response.getMessage()); + } + + return response.getData().getProjectId(); + } +} +``` + +### 3.4 异常处理与事务管理 + +#### 3.4.1 异常处理策略 + +**场景1:流水分析平台调用失败** +- `LsfxAnalysisClient` 抛出 `LsfxApiException` +- Service 层捕获后转换为 `ServiceException` +- Spring 事务自动回滚 +- 用户收到明确错误提示 + +**场景2:网络超时** +- `LsfxAnalysisClient` 配置了超时时间(连接30秒,读取60秒) +- 超时后抛出异常,事务回滚 + +**场景3:响应数据无效** +- Service 层进行业务校验 +- 发现无效响应抛出 `ServiceException` +- 事务回滚 + +#### 3.4.2 事务管理 + +```java +@Transactional(rollbackFor = Exception.class) +public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) { + // 任何一步失败,整个事务自动回滚 + Integer lsfxProjectId = callLsfxPlatform(dto.getProjectName()); + + // ... 数据库操作 ... + + projectMapper.insert(project); + return vo; +} +``` + +**特性:** +- 声明式事务管理 +- 任何异常都触发回滚 +- 保证数据一致性 + +#### 3.4.3 日志记录 + +日志记录已在 `LsfxAnalysisClient.getToken()` 中实现: + +```java +// LsfxAnalysisClient 中的日志 +log.info("【流水分析】获取Token请求: projectNo={}, entityName={}", ...); +log.info("【流水分析】获取Token成功: projectId={}, 耗时={}ms", ...); +log.error("【流水分析】获取Token失败: projectNo={}, error={}", ...); +``` + +Service 层无需重复记录日志。 + +--- + +## 4. 实施步骤 + +### 4.1 实施顺序 + +**步骤1:数据库变更** +```bash +# 执行SQL脚本 +mysql -u root -p ccdi < doc/design/2026-03-04-add-lsfx-project-id.sql +``` + +**步骤2:修改实体类和VO** +- `CcdiProject` 实体类添加 `lsfxProjectId` 字段 +- `CcdiProjectVO` 视图对象添加 `lsfxProjectId` 字段 + +**步骤3:修改 Service 实现** +- `CcdiProjectServiceImpl` 注入 `LsfxAnalysisClient` +- 添加 `callLsfxPlatform()` 私有方法 +- 修改 `createProject()` 方法 + +**步骤4:单元测试** +- 测试正常创建项目流程 +- 测试流水分析平台调用失败场景 +- 测试网络超时场景 + +**步骤5:集成测试** +- 使用 Swagger 测试完整流程 +- 验证数据库中 `lsfx_project_id` 是否正确保存 +- 验证流水分析平台是否成功创建项目 + +**步骤6:前端适配(可选)** +- 如果前端需要展示 `lsfxProjectId`,修改前端页面 + +### 4.2 数据库迁移脚本 + +**文件路径**: `doc/design/2026-03-04-add-lsfx-project-id.sql` + +```sql +-- ==================================== +-- 功能:ccdi_project 表新增流水分析平台项目ID字段 +-- 日期:2026-03-04 +-- 作者:Claude Code +-- ==================================== + +USE ccdi; + +-- 新增字段 +ALTER TABLE `ccdi_project` +ADD COLUMN `lsfx_project_id` INT(11) DEFAULT NULL COMMENT '流水分析平台项目ID' +AFTER `low_risk_count`; + +-- 验证 +SELECT * FROM `ccdi_project` LIMIT 1; +``` + +--- + +## 5. 测试方案 + +### 5.1 单元测试 + +**测试类**: `CcdiProjectServiceImplTest.java` + +```java +@SpringBootTest +public class CcdiProjectServiceImplTest { + + @Resource + private ICcdiProjectService projectService; + + @Resource + private LsfxAnalysisClient lsfxAnalysisClient; + + @Test + public void testCreateProject_Success() { + // 准备数据 + CcdiProjectSaveDTO dto = new CcdiProjectSaveDTO(); + dto.setProjectName("测试项目"); + dto.setDescription("测试描述"); + dto.setConfigType("default"); + + // 执行 + CcdiProjectVO result = projectService.createProject(dto); + + // 验证 + assertNotNull(result); + assertNotNull(result.getProjectId()); + assertNotNull(result.getLsfxProjectId()); + assertEquals("测试项目", result.getProjectName()); + } + + @Test(expected = ServiceException.class) + public void testCreateProject_LsfxFailed() { + // 模拟流水分析平台失败 + // 需要使用 Mock 或关闭 Mock Server + + CcdiProjectSaveDTO dto = new CcdiProjectSaveDTO(); + dto.setProjectName("测试项目"); + + projectService.createProject(dto); + } +} +``` + +### 5.2 集成测试 + +**测试步骤:** + +1. **启动 Mock Server** + ```bash + cd lsfx-mock-server + python app.py + ``` + +2. **访问 Swagger** + ``` + http://localhost:8080/swagger-ui/index.html + ``` + +3. **测试创建项目接口** + - 接口: `POST /ccdi/project` + - 请求体: + ```json + { + "projectName": "测试项目001", + "description": "测试项目描述", + "configType": "default" + } + ``` + +4. **验证响应** + ```json + { + "code": 200, + "msg": "项目创建成功", + "data": { + "projectId": 1, + "projectName": "测试项目001", + "lsfxProjectId": 77, // 应该有值 + ... + } + } + ``` + +5. **验证数据库** + ```sql + SELECT project_id, project_name, lsfx_project_id + FROM ccdi_project + WHERE project_id = 1; + ``` + +### 5.3 异常测试 + +**测试场景:** + +1. **流水分析平台不可用** + - 关闭 Mock Server + - 创建项目应该失败 + - 数据库不应该有新记录 + +2. **网络超时** + - 修改 Mock Server 延迟超过60秒 + - 创建项目应该超时失败 + +3. **返回错误码** + - 修改 Mock Server 返回错误码(如40104) + - 创建项目应该失败并显示对应错误信息 + +--- + +## 6. 风险与注意事项 + +### 6.1 风险分析 + +| 风险 | 影响 | 缓解措施 | +|-----|------|---------| +| 流水分析平台不可用 | 无法创建项目 | 提供明确的错误提示,用户可稍后重试 | +| 网络延迟 | 创建项目缓慢 | 已配置合理超时时间(60秒) | +| projectId 重复 | 无影响 | 不设置唯一索引 | +| 事务回滚失败 | 数据不一致 | 依赖 Spring 事务管理,经过充分验证 | + +### 6.2 注意事项 + +1. **依赖外部服务** + - 流水分析平台必须可用 + - 建议在生产环境做好监控和告警 + +2. **无重试机制** + - 失败需要用户手动重试 + - 可以考虑在 UI 层提供重试按钮 + +3. **项目编号唯一性** + - 使用时间戳保证唯一性 + - 理论上不会重复(毫秒级时间戳) + +4. **前端适配** + - 当前设计不要求前端传 `lsfxProjectId` + - 如果需要展示,修改前端页面即可 + +--- + +## 7. 变更清单 + +| 类型 | 文件 | 变更内容 | 状态 | +|-----|------|---------|------| +| 数据库 | `ccdi_project` 表 | 新增 `lsfx_project_id` 字段 | 待执行 | +| SQL | `2026-03-04-add-lsfx-project-id.sql` | 数据库迁移脚本 | 待创建 | +| 实体类 | `CcdiProject.java` | 新增 `lsfxProjectId` 属性 | 待修改 | +| VO | `CcdiProjectVO.java` | 新增 `lsfxProjectId` 属性 | 待修改 | +| Service | `CcdiProjectServiceImpl.java` | 注入 `LsfxAnalysisClient`,添加调用逻辑 | 待修改 | + +--- + +## 8. 后续优化建议 + +### 8.1 短期优化 + +1. **添加重试机制** + - 在 Service 层添加自动重试(最多3次) + - 使用 Spring Retry 框架 + +2. **异步调用(可选)** + - 如果创建速度成为瓶颈,可改为异步调用 + - 需要增加状态管理和轮询接口 + +### 8.2 长期优化 + +1. **批量创建支持** + - 如果需要批量创建项目,考虑批量调用流水分析平台 + +2. **缓存机制** + - 如果 token 需要重复使用,考虑缓存机制 + - 需要处理 token 过期问题 + +--- + +## 9. 参考资料 + +- 《兰溪-流水分析对接-新版.md》 +- 项目 CLAUDE.md 文档 +- Spring Boot 事务管理文档 +- MyBatis Plus 使用指南 + +--- + +**文档结束**