# 创建项目时集成流水分析平台设计方案

**文档版本**: 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 使用指南

---

**文档结束**
