Files
ccdi/docs/design/2026-03-04-create-project-integrate-lsfx-design.md
wkc d97a34f3b9 docs: 更新设计文档状态并添加实施总结
- 更新设计文档状态为'已实施'
- 添加实施总结文档
- 记录所有变更和测试结果
- 包含Git提交记录和性能分析
2026-03-04 11:15:24 +08:00

555 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 创建项目时集成流水分析平台设计方案
**文档版本**: 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 使用指南
---
**文档结束**