# 创建项目时集成流水分析平台设计方案 **文档版本**: 1.0 **创建日期**: 2026-03-04 **作者**: Claude Code **状态**: ✅ 已实施 **实施日期**: 2026-03-04 **测试状态**: ✅ 测试通过 --- ## 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 使用指南 --- **文档结束**