整理docs目录并补充文档规范
This commit is contained in:
745
docs/plans/backend/2026-03-06-model-param-config-backend.md
Normal file
745
docs/plans/backend/2026-03-06-model-param-config-backend.md
Normal file
@@ -0,0 +1,745 @@
|
||||
# 模型参数配置优化 - 后端实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**目标:** 实现模型参数批量查询和批量保存接口,支持前端统一展示和保存所有模型参数
|
||||
|
||||
**技术栈:** Spring Boot 3.5.8 + MyBatis Plus 3.0.5 + Java 21
|
||||
|
||||
**依赖模块:** ccdi-project
|
||||
|
||||
**预计时间:** 2-3小时
|
||||
|
||||
---
|
||||
|
||||
## 📋 任务概览
|
||||
|
||||
| 任务组 | 任务数 | 说明 |
|
||||
|--------|--------|------|
|
||||
| DTO/VO 创建 | 6个 | 数据传输对象 |
|
||||
| Mapper 层 | 2个 | 数据访问层 |
|
||||
| Service 层 | 5个 | 业务逻辑层 |
|
||||
| Controller 层 | 2个 | API接口层 |
|
||||
| 测试 | 1个 | Swagger测试 |
|
||||
| **总计** | **16个** | |
|
||||
|
||||
---
|
||||
|
||||
## 任务列表
|
||||
|
||||
### Task 1: 创建批量查询请求DTO
|
||||
|
||||
**文件:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamAllQueryDTO.java`
|
||||
|
||||
**步骤 1: 创建类文件**
|
||||
|
||||
```java
|
||||
package com.ruoyi.ccdi.project.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 批量查询所有模型参数DTO
|
||||
*/
|
||||
@Data
|
||||
public class ModelParamAllQueryDTO {
|
||||
|
||||
/** 项目ID(0表示全局配置,>0表示项目配置) */
|
||||
private Long projectId;
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamAllQueryDTO.java
|
||||
git commit -m "feat(ccdi-project): 添加批量查询所有模型参数DTO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 创建模型分组VO
|
||||
|
||||
**文件:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/ModelGroupVO.java`
|
||||
|
||||
**步骤 1: 创建类文件**
|
||||
|
||||
```java
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 模型分组VO(用于按模型分组展示参数)
|
||||
*/
|
||||
@Data
|
||||
public class ModelGroupVO {
|
||||
|
||||
/** 模型编码 */
|
||||
private String modelCode;
|
||||
|
||||
/** 模型名称 */
|
||||
private String modelName;
|
||||
|
||||
/** 参数列表 */
|
||||
private List<ModelParamVO> params;
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/ModelGroupVO.java
|
||||
git commit -m "feat(ccdi-project): 添加模型分组VO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 创建批量查询响应VO
|
||||
|
||||
**文件:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/ModelParamAllVO.java`
|
||||
|
||||
**步骤 1: 创建类文件**
|
||||
|
||||
```java
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量查询所有模型参数响应VO
|
||||
*/
|
||||
@Data
|
||||
public class ModelParamAllVO {
|
||||
|
||||
/** 模型列表(包含每个模型及其参数) */
|
||||
private List<ModelGroupVO> models;
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/ModelParamAllVO.java
|
||||
git commit -m "feat(ccdi-project): 添加批量查询所有模型参数响应VO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 创建批量保存参数项DTO
|
||||
|
||||
**文件:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ParamValueItem.java`
|
||||
|
||||
**步骤 1: 创建类文件**
|
||||
|
||||
```java
|
||||
package com.ruoyi.ccdi.project.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 参数值项DTO
|
||||
*/
|
||||
@Data
|
||||
public class ParamValueItem {
|
||||
|
||||
/** 参数编码 */
|
||||
private String paramCode;
|
||||
|
||||
/** 参数值 */
|
||||
private String paramValue;
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ParamValueItem.java
|
||||
git commit -m "feat(ccdi-project): 添加参数值项DTO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 创建批量保存模型参数组DTO
|
||||
|
||||
**文件:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamGroupDTO.java`
|
||||
|
||||
**步骤 1: 创建类文件**
|
||||
|
||||
```java
|
||||
package com.ruoyi.ccdi.project.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 模型参数分组DTO(用于批量保存)
|
||||
*/
|
||||
@Data
|
||||
public class ModelParamGroupDTO {
|
||||
|
||||
/** 模型编码 */
|
||||
private String modelCode;
|
||||
|
||||
/** 该模型下修改过的参数 */
|
||||
private List<ParamValueItem> params;
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamGroupDTO.java
|
||||
git commit -m "feat(ccdi-project): 添加模型参数分组DTO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 创建批量保存请求DTO
|
||||
|
||||
**文件:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamSaveAllDTO.java`
|
||||
|
||||
**步骤 1: 创建类文件**
|
||||
|
||||
```java
|
||||
package com.ruoyi.ccdi.project.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量保存所有模型参数DTO
|
||||
*/
|
||||
@Data
|
||||
public class ModelParamSaveAllDTO {
|
||||
|
||||
/** 项目ID */
|
||||
private Long projectId;
|
||||
|
||||
/** 所有模型的参数修改(只包含修改过的参数) */
|
||||
private List<ModelParamGroupDTO> models;
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/ModelParamSaveAllDTO.java
|
||||
git commit -m "feat(ccdi-project): 添加批量保存所有模型参数DTO"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 在Mapper接口中添加批量查询方法
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiModelParamMapper.java`
|
||||
|
||||
**步骤 1: 添加方法签名**
|
||||
|
||||
在接口中添加:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 根据项目ID查询所有模型参数
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 参数列表
|
||||
*/
|
||||
List<CcdiModelParam> selectByProjectId(@Param("projectId") Long projectId);
|
||||
```
|
||||
|
||||
**步骤 2: 检查导入**
|
||||
|
||||
确保包含必要的导入:
|
||||
```java
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
```
|
||||
|
||||
**步骤 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiModelParamMapper.java
|
||||
git commit -m "feat(ccdi-project): 在Mapper接口中添加批量查询方法"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 在Mapper XML中添加SQL查询
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml`
|
||||
|
||||
**步骤 1: 添加SQL语句**
|
||||
|
||||
在 `<mapper>` 标签内添加:
|
||||
|
||||
```xml
|
||||
<!-- 根据项目ID查询所有模型参数 -->
|
||||
<select id="selectByProjectId" resultType="CcdiModelParam">
|
||||
SELECT * FROM ccdi_model_param
|
||||
WHERE project_id = #{projectId}
|
||||
ORDER BY model_code, sort_order
|
||||
</select>
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml
|
||||
git commit -m "feat(ccdi-project): 在Mapper XML中添加批量查询SQL"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 9: 在Service接口中添加批量查询方法
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiModelParamService.java`
|
||||
|
||||
**步骤 1: 添加方法签名**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 查询所有模型及其参数(按模型分组)
|
||||
*
|
||||
* @param projectId 项目ID(0表示全局配置)
|
||||
* @return 所有模型的参数配置
|
||||
*/
|
||||
ModelParamAllVO selectAllParams(Long projectId);
|
||||
```
|
||||
|
||||
**步骤 2: 添加导入**
|
||||
|
||||
```java
|
||||
import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO;
|
||||
```
|
||||
|
||||
**步骤 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiModelParamService.java
|
||||
git commit -m "feat(ccdi-project): 在Service接口中添加批量查询方法"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 10: 在Service接口中添加批量保存方法
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiModelParamService.java`
|
||||
|
||||
**步骤 1: 添加方法签名**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 批量保存所有模型的参数修改
|
||||
*
|
||||
* @param saveAllDTO 所有模型的参数修改数据
|
||||
*/
|
||||
void saveAllParams(ModelParamSaveAllDTO saveAllDTO);
|
||||
```
|
||||
|
||||
**步骤 2: 添加导入**
|
||||
|
||||
```java
|
||||
import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveAllDTO;
|
||||
```
|
||||
|
||||
**步骤 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiModelParamService.java
|
||||
git commit -m "feat(ccdi-project): 在Service接口中添加批量保存方法"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 11: 添加Service实现所需的导入语句
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java`
|
||||
|
||||
**步骤 1: 添加导入语句**
|
||||
|
||||
在文件顶部导入区域添加:
|
||||
|
||||
```java
|
||||
import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveAllDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.ModelParamGroupDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.ParamValueItem;
|
||||
import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.ModelGroupVO;
|
||||
import java.util.Comparator;
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
|
||||
git commit -m "feat(ccdi-project): 添加批量操作所需的导入语句"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 12: 实现批量查询方法
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java`
|
||||
|
||||
**步骤 1: 实现 selectAllParams 方法**
|
||||
|
||||
在 `CcdiModelParamServiceImpl` 类中添加方法:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public ModelParamAllVO selectAllParams(Long projectId) {
|
||||
// 1. 参数验证
|
||||
if (projectId == null) {
|
||||
projectId = 0L;
|
||||
}
|
||||
|
||||
// 2. 如果是项目查询,根据 configType 决定查询哪组参数
|
||||
Long effectiveProjectId = projectId;
|
||||
if (projectId > 0) {
|
||||
CcdiProject project = projectMapper.selectById(projectId);
|
||||
if (project == null) {
|
||||
throw new ServiceException("项目不存在");
|
||||
}
|
||||
if ("default".equals(project.getConfigType())) {
|
||||
effectiveProjectId = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 查询所有模型的参数
|
||||
List<CcdiModelParam> allParams = modelParamMapper.selectByProjectId(effectiveProjectId);
|
||||
|
||||
// 4. 按模型分组
|
||||
Map<String, List<CcdiModelParam>> groupedParams = allParams.stream()
|
||||
.collect(Collectors.groupingBy(CcdiModelParam::getModelCode));
|
||||
|
||||
// 5. 转换为VO
|
||||
ModelParamAllVO result = new ModelParamAllVO();
|
||||
List<ModelGroupVO> models = new ArrayList<>();
|
||||
|
||||
groupedParams.forEach((modelCode, params) -> {
|
||||
ModelGroupVO groupVO = new ModelGroupVO();
|
||||
groupVO.setModelCode(modelCode);
|
||||
groupVO.setModelName(params.get(0).getModelName());
|
||||
|
||||
List<ModelParamVO> paramVOs = params.stream()
|
||||
.map(param -> {
|
||||
ModelParamVO vo = new ModelParamVO();
|
||||
BeanUtils.copyProperties(param, vo);
|
||||
return vo;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
groupVO.setParams(paramVOs);
|
||||
models.add(groupVO);
|
||||
});
|
||||
|
||||
// 6. 按模型编码排序(保证固定顺序)
|
||||
models.sort(Comparator.comparing(ModelGroupVO::getModelCode));
|
||||
|
||||
result.setModels(models);
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
|
||||
git commit -m "feat(ccdi-project): 实现批量查询所有模型参数方法"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 13: 实现批量保存方法
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java`
|
||||
|
||||
**步骤 1: 实现 saveAllParams 方法**
|
||||
|
||||
在 `CcdiModelParamServiceImpl` 类中添加方法:
|
||||
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveAllParams(ModelParamSaveAllDTO saveAllDTO) {
|
||||
try {
|
||||
// 1. 参数验证
|
||||
if (saveAllDTO.getProjectId() == null) {
|
||||
throw new ServiceException("项目ID不能为空");
|
||||
}
|
||||
if (saveAllDTO.getModels() == null || saveAllDTO.getModels().isEmpty()) {
|
||||
throw new ServiceException("参数列表不能为空");
|
||||
}
|
||||
|
||||
Long projectId = saveAllDTO.getProjectId();
|
||||
|
||||
// 2. 如果是项目保存,检查是否需要复制默认参数
|
||||
if (projectId > 0) {
|
||||
CcdiProject project = projectMapper.selectById(projectId);
|
||||
if (project == null) {
|
||||
throw new ServiceException("项目不存在");
|
||||
}
|
||||
|
||||
// 如果是首次保存(configType=default),需要复制所有模型的系统默认参数
|
||||
if ("default".equals(project.getConfigType())) {
|
||||
for (ModelParamGroupDTO modelGroup : saveAllDTO.getModels()) {
|
||||
copyDefaultParamsToProject(projectId, modelGroup.getModelCode());
|
||||
}
|
||||
|
||||
// 更新项目配置类型为 custom
|
||||
project.setConfigType("custom");
|
||||
projectMapper.updateById(project);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 批量更新所有模型的参数值
|
||||
for (ModelParamGroupDTO modelGroup : saveAllDTO.getModels()) {
|
||||
for (ParamValueItem item : modelGroup.getParams()) {
|
||||
int updated = modelParamMapper.updateParamValue(
|
||||
projectId,
|
||||
modelGroup.getModelCode(),
|
||||
item.getParamCode(),
|
||||
item.getParamValue()
|
||||
);
|
||||
if (updated == 0) {
|
||||
log.warn("参数不存在或未更新,modelCode={}, paramCode={}",
|
||||
modelGroup.getModelCode(), item.getParamCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (ServiceException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("批量保存模型参数失败", e);
|
||||
throw new ServiceException("批量保存模型参数失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
|
||||
git commit -m "feat(ccdi-project): 实现批量保存所有模型参数方法"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 14: 在Controller中添加批量查询接口
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiModelParamController.java`
|
||||
|
||||
**步骤 1: 添加导入语句**
|
||||
|
||||
在文件顶部添加:
|
||||
|
||||
```java
|
||||
import com.ruoyi.ccdi.project.domain.dto.ModelParamAllQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveAllDTO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO;
|
||||
```
|
||||
|
||||
**步骤 2: 添加接口方法**
|
||||
|
||||
在 `CcdiModelParamController` 类中添加:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 查询所有模型及其参数(按模型分组)
|
||||
*/
|
||||
@Operation(summary = "查询所有模型及其参数")
|
||||
@GetMapping("/listAll")
|
||||
public AjaxResult listAll(@Validated ModelParamAllQueryDTO queryDTO) {
|
||||
ModelParamAllVO result = modelParamService.selectAllParams(queryDTO.getProjectId());
|
||||
return success(result);
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 3: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiModelParamController.java
|
||||
git commit -m "feat(ccdi-project): 在Controller中添加批量查询接口"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 15: 在Controller中添加批量保存接口
|
||||
|
||||
**文件:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiModelParamController.java`
|
||||
|
||||
**步骤 1: 添加接口方法**
|
||||
|
||||
在 `CcdiModelParamController` 类中添加:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 批量保存所有模型的参数修改
|
||||
*/
|
||||
@Operation(summary = "批量保存所有模型参数")
|
||||
@Log(title = "模型参数配置", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/saveAll")
|
||||
public AjaxResult saveAll(@Validated @RequestBody ModelParamSaveAllDTO saveAllDTO) {
|
||||
modelParamService.saveAllParams(saveAllDTO);
|
||||
return success("保存成功");
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 2: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiModelParamController.java
|
||||
git commit -m "feat(ccdi-project): 在Controller中添加批量保存接口"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 16: 使用Swagger测试后端接口
|
||||
|
||||
**检查点:后端开发完成**
|
||||
|
||||
**步骤 1: 启动后端应用**
|
||||
|
||||
提示用户手动启动:
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
**步骤 2: 访问Swagger UI**
|
||||
|
||||
打开浏览器:`http://localhost:8080/swagger-ui/index.html`
|
||||
|
||||
**步骤 3: 测试批量查询接口**
|
||||
|
||||
1. 找到"模型参数配置"分组
|
||||
2. 找到 `GET /ccdi/modelParam/listAll` 接口
|
||||
3. 点击 "Try it out"
|
||||
4. 输入参数:`projectId: 0`
|
||||
5. 点击 "Execute"
|
||||
6. 验证响应:
|
||||
- 状态码:200
|
||||
- 返回数据包含 `models` 数组
|
||||
- 每个模型包含 `modelCode`, `modelName`, `params`
|
||||
|
||||
**预期结果:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"models": [
|
||||
{
|
||||
"modelCode": "LARGE_TRANSACTION",
|
||||
"modelName": "大额交易模型",
|
||||
"params": [
|
||||
{
|
||||
"paramCode": "THRESHOLD_AMOUNT",
|
||||
"paramName": "单笔交易金额阈值",
|
||||
"paramDesc": "单笔交易金额超过此值触发预警",
|
||||
"paramValue": "50000",
|
||||
"paramUnit": "元"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 4: 测试批量保存接口**
|
||||
|
||||
1. 找到 `POST /ccdi/modelParam/saveAll` 接口
|
||||
2. 点击 "Try it out"
|
||||
3. 输入请求体:
|
||||
```json
|
||||
{
|
||||
"projectId": 0,
|
||||
"models": [
|
||||
{
|
||||
"modelCode": "LARGE_TRANSACTION",
|
||||
"params": [
|
||||
{
|
||||
"paramCode": "THRESHOLD_AMOUNT",
|
||||
"paramValue": "60000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
4. 点击 "Execute"
|
||||
5. 验证响应:状态码 200,msg 为 "保存成功"
|
||||
|
||||
**步骤 5: 测试其他场景**
|
||||
|
||||
- 测试项目配置查询(projectId > 0)
|
||||
- 测试首次保存参数复制
|
||||
- 测试多模型同时保存
|
||||
|
||||
**步骤 6: 提交测试记录**
|
||||
|
||||
```bash
|
||||
mkdir -p docs/tests/records
|
||||
git add docs/tests/records/
|
||||
git commit -m "test(ccdi-project): 记录后端接口测试结果"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成标志
|
||||
|
||||
后端实施完成的标志:
|
||||
- ✅ 所有16个任务执行完成
|
||||
- ✅ Swagger接口测试通过
|
||||
- ✅ 代码已提交到git
|
||||
- ✅ 可以响应前端的批量查询和保存请求
|
||||
|
||||
## 📝 后端API说明
|
||||
|
||||
### 批量查询接口
|
||||
- **URL**: `GET /ccdi/modelParam/listAll?projectId=0`
|
||||
- **返回**: 所有模型的参数配置(按模型分组)
|
||||
|
||||
### 批量保存接口
|
||||
- **URL**: `POST /ccdi/modelParam/saveAll`
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"projectId": 0,
|
||||
"models": [
|
||||
{
|
||||
"modelCode": "MODEL_CODE",
|
||||
"params": [
|
||||
{
|
||||
"paramCode": "PARAM_CODE",
|
||||
"paramValue": "NEW_VALUE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- **返回**: `{"code": 200, "msg": "保存成功"}`
|
||||
|
||||
---
|
||||
|
||||
**后端实施计划完成!准备前端开发时,使用前端实施计划。**
|
||||
@@ -0,0 +1,413 @@
|
||||
# Project Detail Transaction Query Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Build the backend query, filter options, detail, and export endpoints for the project detail bank statement page using local `ccdi_bank_statement` data.
|
||||
|
||||
**Architecture:** Extend the existing `ccdi-project` module with a dedicated bank statement controller and service, while reusing the current `CcdiBankStatementMapper` plus XML dynamic SQL. Keep DTO/VO layering strict, compute direction and display amount in the query layer, and export through `ruoyi-common` `ExcelUtil` so the page and export share one query contract.
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, Mockito + JUnit 5, RuoYi `ExcelUtil`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Scaffold DTO/VO contracts and the first failing service test
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiBankStatementQueryDTO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementOptionVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementFilterOptionsVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementListVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementDetailVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankStatementService.java`
|
||||
- Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```java
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiBankStatementServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiBankStatementServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private CcdiBankStatementMapper bankStatementMapper;
|
||||
|
||||
@Test
|
||||
void getFilterOptions_shouldReturnProjectWideOptions() {
|
||||
CcdiBankStatementFilterOptionsVO options = new CcdiBankStatementFilterOptionsVO();
|
||||
options.setOurSubjectOptions(List.of(new CcdiBankStatementOptionVO("主体A", "主体A")));
|
||||
when(bankStatementMapper.selectFilterOptions(100L)).thenReturn(options);
|
||||
|
||||
CcdiBankStatementFilterOptionsVO result = service.getFilterOptions(100L);
|
||||
|
||||
assertEquals(1, result.getOurSubjectOptions().size());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest`
|
||||
|
||||
Expected: FAIL with missing `CcdiBankStatementServiceImpl`, DTO, or mapper methods.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
```java
|
||||
public interface ICcdiBankStatementService {
|
||||
CcdiBankStatementFilterOptionsVO getFilterOptions(Long projectId);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiBankStatementOptionVO {
|
||||
private String label;
|
||||
private String value;
|
||||
|
||||
public CcdiBankStatementOptionVO(String label, String value) {
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add the remaining DTO/VO classes with only the fields already confirmed in the design.
|
||||
|
||||
**Step 4: Run test to verify it still fails only on the missing service implementation**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest`
|
||||
|
||||
Expected: FAIL with `CcdiBankStatementServiceImpl` not found or method not implemented.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiBankStatementQueryDTO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementOptionVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementFilterOptionsVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementListVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiBankStatementDetailVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankStatementService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java
|
||||
git commit -m "新增流水明细查询后端契约与测试骨架"
|
||||
```
|
||||
|
||||
### Task 2: Implement mapper query methods for options, list, detail, and export
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java`
|
||||
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add tests that assert the service delegates to mapper methods with normalized arguments:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void pageQuery_shouldNormalizeSortFieldAndDirection() {
|
||||
Page<CcdiBankStatementListVO> page = new Page<>(1, 10);
|
||||
CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
|
||||
queryDTO.setProjectId(100L);
|
||||
queryDTO.setOrderBy("amount");
|
||||
queryDTO.setOrderDirection("desc");
|
||||
|
||||
service.selectStatementPage(page, queryDTO);
|
||||
|
||||
verify(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest`
|
||||
|
||||
Expected: FAIL because mapper methods `selectStatementPage`, `selectStatementListForExport`, `selectStatementDetailById`, `selectFilterOptions` are missing.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
In `CcdiBankStatementMapper.java`, add:
|
||||
|
||||
```java
|
||||
Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page,
|
||||
@Param("query") CcdiBankStatementQueryDTO query);
|
||||
|
||||
List<CcdiBankStatementListVO> selectStatementListForExport(@Param("query") CcdiBankStatementQueryDTO query);
|
||||
|
||||
CcdiBankStatementDetailVO selectStatementDetailById(@Param("bankStatementId") Long bankStatementId);
|
||||
|
||||
CcdiBankStatementFilterOptionsVO selectFilterOptions(@Param("projectId") Long projectId);
|
||||
```
|
||||
|
||||
In `CcdiBankStatementMapper.xml`, add:
|
||||
|
||||
- a reusable SQL fragment for common filters
|
||||
- a reusable expression for parsed transaction time
|
||||
- a reusable expression for signed display amount
|
||||
- separate selects for page, export, detail, and distinct project options
|
||||
|
||||
**Step 4: Run test to verify it passes or fails only on service logic**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest`
|
||||
|
||||
Expected: mapper-signature errors gone; remaining failures only relate to unimplemented service methods.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java
|
||||
git commit -m "补充流水明细查询Mapper与动态SQL"
|
||||
```
|
||||
|
||||
### Task 3: Implement service normalization, page query, and project-wide filter options
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add focused service tests for:
|
||||
|
||||
- default `tabType=all`
|
||||
- invalid `orderBy` falling back to `trxDate`
|
||||
- invalid `orderDirection` falling back to `desc`
|
||||
- blank string fields trimming to `null`
|
||||
|
||||
```java
|
||||
@Test
|
||||
void normalizeQuery_shouldFallbackToSafeDefaults() {
|
||||
CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
|
||||
queryDTO.setProjectId(100L);
|
||||
queryDTO.setOrderBy("drop table");
|
||||
queryDTO.setOrderDirection("sideways");
|
||||
|
||||
service.selectStatementPage(new Page<>(1, 10), queryDTO);
|
||||
|
||||
assertEquals("trxDate", queryDTO.getOrderBy());
|
||||
assertEquals("desc", queryDTO.getOrderDirection());
|
||||
assertEquals("all", queryDTO.getTabType());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest`
|
||||
|
||||
Expected: FAIL because service normalization logic is missing.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
In `CcdiBankStatementServiceImpl.java`:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
|
||||
@Resource
|
||||
private CcdiBankStatementMapper bankStatementMapper;
|
||||
|
||||
@Override
|
||||
public Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page,
|
||||
CcdiBankStatementQueryDTO queryDTO) {
|
||||
normalizeQuery(queryDTO);
|
||||
return bankStatementMapper.selectStatementPage(page, queryDTO);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Implement `normalizeQuery(queryDTO)` to enforce the white-listed sort fields and normalize blank strings and booleans before delegating to mapper.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest`
|
||||
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java
|
||||
git commit -m "实现流水明细查询服务层规范化逻辑"
|
||||
```
|
||||
|
||||
### Task 4: Add detail lookup and export model with a failing export test
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiBankStatementExcel.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankStatementService.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add a service test for export mapping:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void selectStatementListForExport_shouldMapDisplayColumns() {
|
||||
CcdiBankStatementListVO row = new CcdiBankStatementListVO();
|
||||
row.setTrxDate("2024-02-01 10:33:44");
|
||||
row.setLeAccountNo("6222");
|
||||
row.setLeAccountName("张三");
|
||||
row.setDisplayAmount(new BigDecimal("-8.00"));
|
||||
when(bankStatementMapper.selectStatementListForExport(any())).thenReturn(List.of(row));
|
||||
|
||||
List<CcdiBankStatementExcel> result = service.selectStatementListForExport(new CcdiBankStatementQueryDTO());
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("6222", result.get(0).getLeAccountNo());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest`
|
||||
|
||||
Expected: FAIL because export model and export service method do not exist.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Create `CcdiBankStatementExcel.java` with `@Excel` annotations for:
|
||||
|
||||
- `trxDate`
|
||||
- `leAccountNo`
|
||||
- `leAccountName`
|
||||
- `customerAccountName`
|
||||
- `customerAccountNo`
|
||||
- `userMemo`
|
||||
- `cashType`
|
||||
- `displayAmount`
|
||||
|
||||
Add service methods:
|
||||
|
||||
```java
|
||||
List<CcdiBankStatementExcel> selectStatementListForExport(CcdiBankStatementQueryDTO queryDTO);
|
||||
|
||||
CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId);
|
||||
```
|
||||
|
||||
Map export rows from `CcdiBankStatementListVO` to `CcdiBankStatementExcel`.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest`
|
||||
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiBankStatementExcel.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankStatementService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java
|
||||
git commit -m "实现流水明细导出模型与详情查询"
|
||||
```
|
||||
|
||||
### Task 5: Add controller endpoints and the first failing controller test
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java`
|
||||
- Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementControllerTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```java
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiBankStatementControllerTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiBankStatementController controller;
|
||||
|
||||
@Mock
|
||||
private ICcdiBankStatementService bankStatementService;
|
||||
|
||||
@Test
|
||||
void options_shouldReturnAjaxResultSuccess() {
|
||||
when(bankStatementService.getFilterOptions(100L)).thenReturn(new CcdiBankStatementFilterOptionsVO());
|
||||
|
||||
AjaxResult result = controller.getOptions(100L);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementControllerTest`
|
||||
|
||||
Expected: FAIL because controller class and methods do not exist.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Add endpoints:
|
||||
|
||||
- `GET /ccdi/project/bank-statement/list`
|
||||
- `GET /ccdi/project/bank-statement/options`
|
||||
- `GET /ccdi/project/bank-statement/detail/{bankStatementId}`
|
||||
- `POST /ccdi/project/bank-statement/export`
|
||||
|
||||
Use `TableSupport.buildPageRequest()` for paging and `ExcelUtil<CcdiBankStatementExcel>` for export:
|
||||
|
||||
```java
|
||||
ExcelUtil<CcdiBankStatementExcel> util = new ExcelUtil<>(CcdiBankStatementExcel.class);
|
||||
util.exportExcel(response, list, "流水明细");
|
||||
```
|
||||
|
||||
Guard permissions with:
|
||||
|
||||
- query/list/detail/options: `ccdi:project:query`
|
||||
- export: `ccdi:project:export`
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementControllerTest`
|
||||
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementControllerTest.java
|
||||
git commit -m "新增流水明细查询控制器接口"
|
||||
```
|
||||
|
||||
### Task 6: Verify the backend end-to-end inside the module
|
||||
|
||||
**Files:**
|
||||
- Modify if needed after failures: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java`
|
||||
- Modify if needed after failures: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java`
|
||||
- Modify if needed after failures: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml`
|
||||
|
||||
**Step 1: Run the focused backend tests**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest,CcdiBankStatementControllerTest`
|
||||
|
||||
Expected: PASS
|
||||
|
||||
**Step 2: Run the module compile**
|
||||
|
||||
Run: `mvn clean compile -pl ccdi-project -am`
|
||||
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
**Step 3: Smoke-check the mapper XML and endpoint contracts**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -Dtest=CcdiBankStatementServiceImplTest -q
|
||||
```
|
||||
|
||||
Expected: PASS without XML binding errors or missing mapper statements.
|
||||
|
||||
**Step 4: Fix any compile or binding failures with the smallest possible patch**
|
||||
|
||||
Typical fixes:
|
||||
|
||||
- wrong `@Param("query")` names
|
||||
- missing VO field aliases in SQL
|
||||
- `Page<>` generic mismatch
|
||||
- `ExcelUtil` export field annotation issues
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImpl.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankStatementServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementControllerTest.java
|
||||
git commit -m "完成流水明细查询后端实现与校验"
|
||||
```
|
||||
@@ -0,0 +1,493 @@
|
||||
# Project Detail Pull Bank Info Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Build the backend parse-and-pull workflow for the project detail “拉取本行信息” modal, including ID-card Excel parsing, task submission, thread-pool scheduling, file-upload record updates, and bank-statement ingestion reuse.
|
||||
|
||||
**Architecture:** Extend the existing `CcdiFileUploadController` and `CcdiFileUploadServiceImpl` instead of creating a second task subsystem. Parse身份证文件 on demand, persist one `ccdi_file_upload_record` per身份证 with `accountNos` initialized to the身份证号, then reuse the existing `fileUploadExecutor` plus a shared “logId ready” pipeline to update file name, polling status, and bank statements consistently for both uploaded files and pulled inner-flow tasks.
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, EasyExcel 3.3.4, JUnit 5, Mockito
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add parse/submit contracts and write the first failing parse test
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiPullBankInfoSubmitDTO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiIdCardParseVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiIdCardExcelRow.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
在 `CcdiFileUploadServiceImplTest` 中新增身份证文件解析测试,验证“首个 sheet 第一列、忽略表头、空行、重复值”:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void parseIdCardFile_shouldReadFirstSheetFirstColumnAndDeduplicate() throws Exception {
|
||||
MultipartFile file = createIdCardExcel(
|
||||
"身份证号",
|
||||
"110101199001018888",
|
||||
"",
|
||||
"110101199001018888",
|
||||
"110101199001019999"
|
||||
);
|
||||
|
||||
List<String> result = service.parseIdCardFile(file);
|
||||
|
||||
assertEquals(List.of("110101199001018888", "110101199001019999"), result);
|
||||
}
|
||||
```
|
||||
|
||||
再补一个非法身份证失败测试:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void parseIdCardFile_shouldRejectInvalidIdCard() throws Exception {
|
||||
MultipartFile file = createIdCardExcel("身份证号", "123456");
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class, () -> service.parseIdCardFile(file));
|
||||
|
||||
assertTrue(exception.getMessage().contains("身份证"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
||||
|
||||
Expected: FAIL because `parseIdCardFile` and the new DTO / VO / Excel row model do not exist yet.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 `ICcdiFileUploadService` 中新增方法:
|
||||
|
||||
```java
|
||||
List<String> parseIdCardFile(MultipartFile file);
|
||||
```
|
||||
|
||||
创建 `CcdiIdCardExcelRow`:
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class CcdiIdCardExcelRow {
|
||||
|
||||
@ExcelProperty(index = 0)
|
||||
private String idCard;
|
||||
}
|
||||
```
|
||||
|
||||
在 `CcdiFileUploadServiceImpl` 中用 EasyExcel 实现最小可用解析逻辑:
|
||||
|
||||
```java
|
||||
List<CcdiIdCardExcelRow> rows = EasyExcel.read(file.getInputStream(), CcdiIdCardExcelRow.class)
|
||||
.sheet(0)
|
||||
.headRowNumber(1)
|
||||
.doReadSync();
|
||||
```
|
||||
|
||||
然后:
|
||||
|
||||
- `trim()` 去空白
|
||||
- 过滤空值
|
||||
- 用 `LinkedHashSet` 去重保序
|
||||
- 使用 18 位身份证正则校验
|
||||
- 当无有效身份证时抛出 `RuntimeException("首个sheet第一列未解析到有效身份证号")`
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
||||
|
||||
Expected: PASS for the new parse tests; existing tests remain green.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiPullBankInfoSubmitDTO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiIdCardParseVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiIdCardExcelRow.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "新增拉取本行信息参数模型与身份证解析能力"
|
||||
```
|
||||
|
||||
### Task 2: Add pull-bank-info submission logic and make record initialization fail first
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增“提交拉取任务时先插入上传记录”的测试,验证 `accountNos` 和 `uploadUser` 初始化正确:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void submitPullBankInfo_shouldInsertUploadingRecordsWithIdCardAsAccountNo() {
|
||||
CcdiProject project = new CcdiProject();
|
||||
project.setProjectId(PROJECT_ID);
|
||||
project.setLsfxProjectId(LSFX_PROJECT_ID);
|
||||
when(projectMapper.selectById(PROJECT_ID)).thenReturn(project);
|
||||
|
||||
AtomicReference<List<CcdiFileUploadRecord>> inserted = new AtomicReference<>();
|
||||
doAnswer(invocation -> {
|
||||
List<CcdiFileUploadRecord> records = invocation.getArgument(0);
|
||||
for (int i = 0; i < records.size(); i++) {
|
||||
records.get(i).setId((long) (i + 1));
|
||||
}
|
||||
inserted.set(new ArrayList<>(records));
|
||||
return records.size();
|
||||
}).when(recordMapper).insertBatch(any());
|
||||
|
||||
TransactionSynchronizationManager.initSynchronization();
|
||||
try {
|
||||
String batchId = service.submitPullBankInfo(
|
||||
PROJECT_ID,
|
||||
List.of("110101199001018888", "110101199001019999"),
|
||||
"2026-03-01",
|
||||
"2026-03-10",
|
||||
9527L,
|
||||
"admin"
|
||||
);
|
||||
|
||||
assertNotNull(batchId);
|
||||
assertEquals("110101199001018888", inserted.get().get(0).getAccountNos());
|
||||
assertEquals("admin", inserted.get().get(0).getUploadUser());
|
||||
assertEquals("uploading", inserted.get().get(0).getFileStatus());
|
||||
assertEquals(1, TransactionSynchronizationManager.getSynchronizations().size());
|
||||
} finally {
|
||||
TransactionSynchronizationManager.clearSynchronization();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
||||
|
||||
Expected: FAIL because `submitPullBankInfo` does not exist and `insertBatch` SQL does not persist `accountNos`.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 `ICcdiFileUploadService` 中新增方法:
|
||||
|
||||
```java
|
||||
String submitPullBankInfo(Long projectId,
|
||||
List<String> idCards,
|
||||
String startDate,
|
||||
String endDate,
|
||||
Long userId,
|
||||
String username);
|
||||
```
|
||||
|
||||
在 `CcdiFileUploadServiceImpl` 中实现:
|
||||
|
||||
- 校验项目存在且带有 `lsfxProjectId`
|
||||
- 校验日期非空、开始日期不大于结束日期
|
||||
- 校验身份证集合非空
|
||||
- 为每个身份证创建一条 `CcdiFileUploadRecord`
|
||||
- `record.setAccountNos(idCard);`
|
||||
- `record.setFileName(idCard);`
|
||||
- `record.setUploadUser(username);`
|
||||
- `record.setFileStatus("uploading");`
|
||||
- `record.setUploadTime(new Date());`
|
||||
|
||||
在 `CcdiFileUploadRecordMapper.xml` 的 `insertBatch` 中补上 `account_nos`:
|
||||
|
||||
```xml
|
||||
insert into ccdi_file_upload_record (
|
||||
project_id, lsfx_project_id, file_name, file_size, file_status,
|
||||
enterprise_names, account_nos, upload_time, upload_user
|
||||
)
|
||||
```
|
||||
|
||||
注册事务提交后的异步调度:
|
||||
|
||||
```java
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
CompletableFuture.runAsync(() -> submitPullBankInfoTasks(...));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
||||
|
||||
Expected: PASS for the new submission test and no regression in existing upload tests.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "实现拉取本行信息任务提交与记录初始化"
|
||||
```
|
||||
|
||||
### Task 3: Refactor the shared logId pipeline and add the first failing pull-flow test
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增“拉取行内流水拿到 `logId` 后复用公共流水线”的测试,先验证文件名回写和最终成功:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void processPullBankInfoAsync_shouldUpdateFileNameFromStatusResponse() {
|
||||
when(lsfxClient.fetchInnerFlow(any())).thenReturn(buildFetchInnerFlowResponse(LOG_ID));
|
||||
when(lsfxClient.checkParseStatus(LSFX_PROJECT_ID, String.valueOf(LOG_ID)))
|
||||
.thenReturn(buildCheckParseStatusResponse(false));
|
||||
when(lsfxClient.getFileUploadStatus(any())).thenReturn(buildParsedSuccessStatusResponse("XX身份证.xlsx"));
|
||||
when(lsfxClient.getBankStatement(any(GetBankStatementRequest.class)))
|
||||
.thenReturn(buildEmptyBankStatementResponse());
|
||||
|
||||
CcdiFileUploadRecord record = buildRecord();
|
||||
service.processPullBankInfoAsync(
|
||||
PROJECT_ID,
|
||||
LSFX_PROJECT_ID,
|
||||
record,
|
||||
"110101199001018888",
|
||||
"2026-03-01",
|
||||
"2026-03-10",
|
||||
9527L
|
||||
);
|
||||
|
||||
verify(recordMapper, atLeastOnce()).updateById(argThat(item ->
|
||||
"XX身份证.xlsx".equals(item.getFileName())));
|
||||
verify(recordMapper, atLeastOnce()).updateById(argThat(item ->
|
||||
"parsed_success".equals(item.getFileStatus())));
|
||||
}
|
||||
```
|
||||
|
||||
再补一个失败测试,验证 `fetchInnerFlow` 异常只影响当前记录:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void processPullBankInfoAsync_shouldMarkParsedFailedWhenFetchInnerFlowThrows() {
|
||||
when(lsfxClient.fetchInnerFlow(any())).thenThrow(new RuntimeException("fetch inner flow failed"));
|
||||
|
||||
CcdiFileUploadRecord record = buildRecord();
|
||||
service.processPullBankInfoAsync(
|
||||
PROJECT_ID,
|
||||
LSFX_PROJECT_ID,
|
||||
record,
|
||||
"110101199001018888",
|
||||
"2026-03-01",
|
||||
"2026-03-10",
|
||||
9527L
|
||||
);
|
||||
|
||||
verify(recordMapper, atLeastOnce()).updateById(argThat(item ->
|
||||
"parsed_failed".equals(item.getFileStatus())));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
||||
|
||||
Expected: FAIL because `processPullBankInfoAsync` and the shared “logId ready” pipeline do not exist yet.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 `CcdiFileUploadServiceImpl` 中拆分两段逻辑:
|
||||
|
||||
1. 文件来源阶段
|
||||
- `processFileAsync(...)` 中只负责上传文件并拿到 `logId`
|
||||
- `processPullBankInfoAsync(...)` 中只负责调用 `fetchInnerFlow` 并拿到 `logId`
|
||||
|
||||
2. 公共处理阶段
|
||||
- 新增 `processRecordAfterLogIdReady(...)`
|
||||
|
||||
公共处理方法至少负责:
|
||||
|
||||
```java
|
||||
private void processRecordAfterLogIdReady(Long projectId,
|
||||
Integer lsfxProjectId,
|
||||
CcdiFileUploadRecord record,
|
||||
Integer logId) {
|
||||
record.setLogId(logId);
|
||||
record.setFileStatus("parsing");
|
||||
recordMapper.updateById(record);
|
||||
boolean parsingComplete = waitForParsingComplete(lsfxProjectId, logId.toString());
|
||||
GetFileUploadStatusResponse statusResponse = lsfxClient.getFileUploadStatus(statusRequest);
|
||||
String fileName = StringUtils.hasText(logItem.getUploadFileName())
|
||||
? logItem.getUploadFileName()
|
||||
: logItem.getDownloadFileName();
|
||||
record.setFileName(fileName);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
`processPullBankInfoAsync(...)` 最小实现:
|
||||
|
||||
```java
|
||||
FetchInnerFlowRequest request = new FetchInnerFlowRequest();
|
||||
request.setGroupId(lsfxProjectId);
|
||||
request.setCustomerNo(idCard);
|
||||
request.setDataChannelCode("ZJRCU");
|
||||
request.setRequestDateId(Integer.parseInt(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)));
|
||||
request.setDataStartDateId(Integer.parseInt(startDate.replace("-", "")));
|
||||
request.setDataEndDateId(Integer.parseInt(endDate.replace("-", "")));
|
||||
request.setUploadUserId(userId.intValue());
|
||||
```
|
||||
|
||||
从 `FetchInnerFlowResponse.getData()` 里取第一个 `logId` 后进入公共处理方法。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
||||
|
||||
Expected: PASS for the new pull-flow tests and the existing file-upload tests.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "抽取logId后公共处理流水线并接入本行拉取"
|
||||
```
|
||||
|
||||
### Task 4: Add controller endpoints and fail on controller tests first
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
|
||||
- Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
为解析接口和提交接口各写一个控制器测试:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void parseIdCardFile_shouldReturnAjaxResultSuccess() {
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"ids.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"test".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
when(fileUploadService.parseIdCardFile(file)).thenReturn(List.of("110101199001018888"));
|
||||
|
||||
AjaxResult result = controller.parseIdCardFile(file);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@Test
|
||||
void pullBankInfo_shouldUseCurrentLoginUserInfo() {
|
||||
CcdiPullBankInfoSubmitDTO dto = new CcdiPullBankInfoSubmitDTO();
|
||||
dto.setProjectId(PROJECT_ID);
|
||||
dto.setIdCards(List.of("110101199001018888"));
|
||||
dto.setStartDate("2026-03-01");
|
||||
dto.setEndDate("2026-03-10");
|
||||
|
||||
try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
|
||||
mocked.when(SecurityUtils::getUserId).thenReturn(9527L);
|
||||
mocked.when(SecurityUtils::getUserName).thenReturn("admin");
|
||||
when(fileUploadService.submitPullBankInfo(PROJECT_ID, dto.getIdCards(), "2026-03-01", "2026-03-10", 9527L, "admin"))
|
||||
.thenReturn("batch-1");
|
||||
|
||||
AjaxResult result = controller.pullBankInfo(dto);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadControllerTest`
|
||||
|
||||
Expected: FAIL because the new controller methods do not exist yet.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 `CcdiFileUploadController` 中新增接口:
|
||||
|
||||
```java
|
||||
@PostMapping("/parse-id-card-file")
|
||||
public AjaxResult parseIdCardFile(@RequestParam MultipartFile file) {
|
||||
List<String> idCards = fileUploadService.parseIdCardFile(file);
|
||||
return AjaxResult.success("解析成功", new CcdiIdCardParseVO(idCards, idCards.size()));
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@PostMapping("/pull-bank-info")
|
||||
public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
String username = SecurityUtils.getUserName();
|
||||
String batchId = fileUploadService.submitPullBankInfo(
|
||||
dto.getProjectId(),
|
||||
dto.getIdCards(),
|
||||
dto.getStartDate(),
|
||||
dto.getEndDate(),
|
||||
userId,
|
||||
username
|
||||
);
|
||||
return AjaxResult.success("拉取任务已提交", batchId);
|
||||
}
|
||||
```
|
||||
|
||||
补上参数校验和 Swagger 注释。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadControllerTest`
|
||||
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java
|
||||
git commit -m "新增拉取本行信息后端接口"
|
||||
```
|
||||
|
||||
### Task 5: Verify the backend end-to-end inside the module
|
||||
|
||||
**Files:**
|
||||
- Modify if needed after failures: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
|
||||
- Modify if needed after failures: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Modify if needed after failures: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml`
|
||||
- Modify if needed after failures: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
- Modify if needed after failures: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java`
|
||||
|
||||
**Step 1: Run focused backend tests**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest,CcdiFileUploadControllerTest`
|
||||
|
||||
Expected: PASS
|
||||
|
||||
**Step 2: Run module compile**
|
||||
|
||||
Run: `mvn clean compile -pl ccdi-project -am`
|
||||
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
**Step 3: Run the existing upload regression tests**
|
||||
|
||||
Run: `mvn test -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest`
|
||||
|
||||
Expected: PASS with no regression on file-upload flow, no MyBatis binding error, and no missing mapper statements.
|
||||
|
||||
**Step 4: Fix the smallest failing point if verification breaks**
|
||||
|
||||
优先排查:
|
||||
|
||||
- `insertBatch` 的 XML 列顺序与实体字段不一致
|
||||
- `uploadFileName` / `downloadFileName` 回写逻辑遗漏空值判断
|
||||
- `FetchInnerFlowResponse` 取 `logId` 时未处理空列表
|
||||
- `Long userId` 转 `Integer uploadUserId` 时的空值或溢出保护
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java
|
||||
git commit -m "完成拉取本行信息后端实现与校验"
|
||||
```
|
||||
@@ -0,0 +1,354 @@
|
||||
# Employee Asset Maintenance Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Add backend support for employee asset maintenance, including aggregated employee detail/save, employee-asset cascade delete by employee family ID card, and asynchronous asset import that auto-resolves the owning employee from the asset holder ID card.
|
||||
|
||||
**Architecture:** Keep employee maintenance on the existing `CcdiBaseStaff` aggregate, and introduce a new `CcdiAssetInfo` resource in `ccdi-info-collection`. Asset rows use `family_id` for the owning employee ID card and `person_id` for the actual holder ID card. Employee add/edit/detail/delete will orchestrate asset persistence by `family_id` inside transactions, while asset import will mirror the existing employee import flow and resolve `family_id` from either the employee table or the employee family-relation table.
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, XML mapper SQL, EasyExcel, Redis, Maven
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add the asset table SQL and document the data contract
|
||||
|
||||
**Files:**
|
||||
- Create: `sql/2026-03-12_ccdi_asset_info.sql`
|
||||
- Review: `assets/资产信息表.csv`
|
||||
- Review: `docs/design/2026-03-12-employee-asset-maintenance-design.md`
|
||||
|
||||
**Step 1: Write the SQL script**
|
||||
|
||||
Create `ccdi_asset_info` with:
|
||||
|
||||
- `asset_id BIGINT` auto increment primary key
|
||||
- `family_id VARCHAR(18)` storing owning employee `id_card`
|
||||
- `person_id VARCHAR(18)` storing actual asset holder `id_card`
|
||||
- business columns from the approved design
|
||||
- audit columns `create_by`, `create_time`, `update_by`, `update_time`
|
||||
- indexes `idx_family_id`, `idx_person_id`, and `idx_asset_main_type`
|
||||
|
||||
**Step 2: Verify the script matches the approved constraints**
|
||||
|
||||
Confirm the script:
|
||||
|
||||
- does not include `asset_id` in any import-facing note
|
||||
- keeps the field names `family_id` and `person_id`
|
||||
- stores owning employee linkage in `family_id`
|
||||
- stores actual holder linkage in `person_id`
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add sql/2026-03-12_ccdi_asset_info.sql docs/design/2026-03-12-employee-asset-maintenance-design.md
|
||||
git commit -m "新增员工资产信息设计与建表脚本"
|
||||
```
|
||||
|
||||
### Task 2: Add the asset domain, DTO, VO, Excel, and mapper skeletons
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiAssetInfo.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiAssetInfoDTO.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiAssetInfoVO.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java`
|
||||
- Create: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml`
|
||||
|
||||
**Step 1: Create the entity**
|
||||
|
||||
Model `CcdiAssetInfo` with MyBatis Plus annotations and audit fields consistent with existing `CcdiBaseStaff`.
|
||||
|
||||
**Step 2: Create request and response objects**
|
||||
|
||||
Add one DTO for nested employee-form submission and one VO for employee detail response.
|
||||
|
||||
**Step 3: Create the Excel and failure record objects**
|
||||
|
||||
`CcdiAssetInfoExcel` must exclude `asset_id` and include `person_id`.
|
||||
|
||||
It must not require `family_id` in the import template because `family_id` is resolved automatically during import.
|
||||
|
||||
**Step 4: Create mapper methods**
|
||||
|
||||
Define methods for:
|
||||
|
||||
- query by `family_id`
|
||||
- delete by `family_id`
|
||||
- delete by `family_id` list
|
||||
- query by `person_id`
|
||||
- batch insert
|
||||
- import lookup by employee `id_card`
|
||||
- import lookup by employee family-relation ID card
|
||||
|
||||
### Task 3: Extend employee DTO and VO aggregation
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffAddDTO.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiBaseStaffEditDTO.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiBaseStaffVO.java`
|
||||
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceAssetAggregationTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add a focused service test that asserts employee detail, add DTO, and edit DTO support `assetInfoList`.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiBaseStaffServiceAssetAggregationTest
|
||||
```
|
||||
|
||||
Expected: FAIL because `assetInfoList` does not exist yet.
|
||||
|
||||
**Step 3: Add the aggregate fields**
|
||||
|
||||
Add `List<CcdiAssetInfoDTO>` to add/edit DTOs and `List<CcdiAssetInfoVO>` to the employee VO.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiBaseStaffServiceAssetAggregationTest
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
### Task 4: Add asset service interfaces and focused persistence methods
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoService.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoImportService.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoServiceImpl.java`
|
||||
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Cover these behaviors:
|
||||
|
||||
- query all assets by `family_id`
|
||||
- replace all assets for one employee
|
||||
- delete assets by one or more employee ID cards
|
||||
- ignore empty rows when replacing assets
|
||||
- preserve `person_id` as the actual holder while forcing `family_id` to the owning employee ID card
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiAssetInfoServiceImplTest
|
||||
```
|
||||
|
||||
Expected: FAIL because the asset service does not exist yet.
|
||||
|
||||
**Step 3: Implement minimal service logic**
|
||||
|
||||
Implement methods:
|
||||
|
||||
- `selectByFamilyId`
|
||||
- `replaceByFamilyId`
|
||||
- `deleteByFamilyId`
|
||||
- `deleteByFamilyIds`
|
||||
|
||||
Use batch delete + batch insert.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiAssetInfoServiceImplTest
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
### Task 5: Update employee service to aggregate asset query and transactional save
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffService.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffServiceImpl.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffController.java`
|
||||
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add or extend service tests for:
|
||||
|
||||
- add employee with multiple assets
|
||||
- edit employee and replace assets
|
||||
- edit employee with changed `id_card`
|
||||
- detail query returns `assetInfoList`
|
||||
- delete employee cascades asset deletion
|
||||
- employee self-owned asset uses `family_id = person_id = employee.idCard`
|
||||
- employee family asset uses `family_id = employee.idCard` and `person_id = relative.idCard`
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiBaseStaffServiceImplTest
|
||||
```
|
||||
|
||||
Expected: FAIL because employee service does not yet coordinate asset persistence.
|
||||
|
||||
**Step 3: Implement the aggregate behavior**
|
||||
|
||||
Update `CcdiBaseStaffServiceImpl` to:
|
||||
|
||||
- inject `ICcdiAssetInfoService`
|
||||
- load assets during detail query by `family_id = employee.id_card`
|
||||
- save employee first, then replace assets by current employee `id_card`
|
||||
- for each asset row, set `family_id = employee.id_card` and preserve submitted `person_id`
|
||||
- capture old `id_card` during edit and clean old asset records when it changes
|
||||
- delete asset rows before deleting employee rows
|
||||
|
||||
**Step 4: Run the focused test again**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiBaseStaffServiceImplTest
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
### Task 6: Add asset import controller and async import service
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiAssetInfoImportService.java`
|
||||
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Cover:
|
||||
|
||||
- import template entity excludes `asset_id`
|
||||
- import template does not require `family_id`
|
||||
- import resolves `family_id` when `person_id` matches an employee `id_card`
|
||||
- import resolves `family_id` when `person_id` matches an employee family-relation ID card
|
||||
- import fails when `person_id` does not match either an employee or an employee family-relation ID card
|
||||
- import fails when one `person_id` maps to multiple employees
|
||||
- import stores failure records only for bad rows
|
||||
- import status and failure list use dedicated asset task keys
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiAssetInfoImportServiceImplTest
|
||||
```
|
||||
|
||||
Expected: FAIL because no asset import pipeline exists yet.
|
||||
|
||||
**Step 3: Implement the import flow**
|
||||
|
||||
Mirror the employee import design with asset-specific names:
|
||||
|
||||
- Redis key prefix `import:assetInfo:`
|
||||
- asynchronous import execution
|
||||
- failure record caching for 7 days
|
||||
- controller endpoints for template, upload, status, and failures
|
||||
- resolve `family_id` automatically from `person_id`
|
||||
|
||||
**Step 4: Run the focused test again**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiAssetInfoImportServiceImplTest
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
### Task 7: Add mapper XML SQL for batch asset operations
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml`
|
||||
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapperTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Verify:
|
||||
|
||||
- select by `family_id`
|
||||
- select by `person_id`
|
||||
- batch insert multiple assets
|
||||
- delete by one `family_id`
|
||||
- delete by multiple `family_id` values
|
||||
- employee existence lookup by `id_card`
|
||||
- employee family-relation existence lookup by relative `id_card`
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiAssetInfoMapperTest
|
||||
```
|
||||
|
||||
Expected: FAIL because the mapper XML SQL is incomplete.
|
||||
|
||||
**Step 3: Implement minimal XML SQL**
|
||||
|
||||
Add:
|
||||
|
||||
- result map
|
||||
- select by `family_id`
|
||||
- select by `person_id`
|
||||
- delete by `family_id`
|
||||
- delete by `family_id` list
|
||||
- batch insert
|
||||
- employee existence lookup
|
||||
- employee family-relation lookup
|
||||
|
||||
**Step 4: Run the mapper test**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiAssetInfoMapperTest
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
### Task 8: Verify the backend changes end to end
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/`
|
||||
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/`
|
||||
- Modify: `sql/2026-03-12_ccdi_asset_info.sql`
|
||||
|
||||
**Step 1: Run focused backend tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiBaseStaffServiceAssetAggregationTest,CcdiAssetInfoServiceImplTest,CcdiBaseStaffServiceImplTest,CcdiAssetInfoImportServiceImplTest,CcdiAssetInfoMapperTest
|
||||
```
|
||||
|
||||
Expected: all focused tests pass.
|
||||
|
||||
**Step 2: Run module compile verification**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
Expected: compile succeeds without Java or mapper XML errors.
|
||||
|
||||
**Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add sql/2026-03-12_ccdi_asset_info.sql ccdi-info-collection/src/main/java/com/ruoyi/info/collection ccdi-info-collection/src/main/resources/mapper/info/collection docs/plans/backend/2026-03-12-employee-asset-maintenance-backend-implementation.md
|
||||
git commit -m "新增员工资产信息后端实施计划"
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# Pull Bank Info Date Limit Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Confirm the new date restriction is fully handled in the frontend and does not require backend changes.
|
||||
|
||||
**Architecture:** The allowed date window is enforced in the Vue dialog before the request is submitted. The backend request contract remains `projectId`, `idCards`, `startDate`, and `endDate`, so this plan records a no-op backend implementation boundary and the verification needed to avoid accidental API changes.
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, Maven, existing `ccdi-project` upload APIs
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Verify backend impact is zero
|
||||
|
||||
**Files:**
|
||||
- Review: `docs/design/2026-03-12-pull-bank-info-date-limit-design.md`
|
||||
- Review: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/`
|
||||
- Review: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/`
|
||||
|
||||
**Step 1: Confirm bug scope**
|
||||
|
||||
Check that the requirement only changes frontend date selection and frontend submit validation.
|
||||
|
||||
**Step 2: Verify request contract stays unchanged**
|
||||
|
||||
Confirm the request still submits the same fields:
|
||||
|
||||
- `projectId`
|
||||
- `idCards`
|
||||
- `startDate`
|
||||
- `endDate`
|
||||
|
||||
**Step 3: Keep backend code unchanged**
|
||||
|
||||
Do not modify controller, service, mapper, DTO, or test classes for this task.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Pull Bank Info Upload Button Hit Area Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Confirm this UI optimization does not require backend changes and document the verification boundary.
|
||||
|
||||
**Architecture:** The issue is caused by the frontend dialog structure and scoped styles in the upload page. Backend APIs, request payloads, and parsing logic remain unchanged, so this plan only records the no-op backend conclusion and the checks needed to avoid accidental interface regressions.
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, Maven, existing `ccdi-project` upload APIs
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Verify backend impact is zero
|
||||
|
||||
**Files:**
|
||||
- Review: `docs/design/2026-03-12-pull-bank-info-upload-button-hit-area-design.md`
|
||||
- Review: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/`
|
||||
- Review: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/`
|
||||
|
||||
**Step 1: Confirm the bug scope**
|
||||
|
||||
Check that the reported problem is limited to the frontend dialog button hit area and layout, not request construction or backend response handling.
|
||||
|
||||
**Step 2: Verify no API contract changes are needed**
|
||||
|
||||
Review the existing pull-bank-info request fields and confirm the dialog still submits the same `projectId`, `idCards`, `startDate`, and `endDate`.
|
||||
|
||||
**Step 3: Keep backend code unchanged**
|
||||
|
||||
Do not modify controller, service, mapper, or DTO classes for this task.
|
||||
|
||||
**Step 4: Run targeted regression verification if frontend payload changes are suspected**
|
||||
|
||||
Run only if implementation unexpectedly touches request assembly:
|
||||
|
||||
```bash
|
||||
mvn test -Dtest=CcdiFileUploadServiceImplTest
|
||||
```
|
||||
|
||||
Expected: related backend tests pass and no interface behavior changes are introduced.
|
||||
@@ -0,0 +1,310 @@
|
||||
# 员工亲属资产维护后端实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 为员工亲属关系维护页面补齐亲属资产的后端聚合保存、详情查询、删除级联和异步导入能力。
|
||||
|
||||
**Architecture:** 以 `CcdiStaffFmyRelation` 为聚合根扩展 `assetInfoList`,由资产模块负责持久化和导入,由亲属关系服务负责在新增、编辑、删除、详情等场景中协调资产数据。编辑时通过固定的 `family_id + person_id` 覆盖重建亲属资产,不引入额外关联字段。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, Redis, EasyExcel, Lombok
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 补齐资产表 SQL 与索引设计
|
||||
|
||||
**Files:**
|
||||
- Create: `D:\ccdi\ccdi\sql\ccdi_asset_info.sql`
|
||||
- Check: `D:\ccdi\ccdi\docs\plans\2026-03-12-staff-family-asset-maintenance-design.md`
|
||||
|
||||
**Step 1: 写出资产表建表 SQL**
|
||||
|
||||
- 新增 `ccdi_asset_info`
|
||||
- 主键使用 `asset_id BIGINT AUTO_INCREMENT`
|
||||
- 字段包含 `family_id`、`person_id` 和资产业务字段
|
||||
- 审计字段遵循项目现有风格
|
||||
|
||||
**Step 2: 为归属查询补索引**
|
||||
|
||||
- 添加 `idx_family_id`
|
||||
- 添加 `idx_person_id`
|
||||
- 添加联合索引 `idx_family_person`
|
||||
|
||||
**Step 3: 自查字段口径**
|
||||
|
||||
- 确认 `family_id` 保存员工证件号
|
||||
- 确认 `person_id` 保存亲属证件号
|
||||
- 确认不新增 `relation_id`
|
||||
|
||||
**Step 4: 记录执行命令**
|
||||
|
||||
Run: `Get-Content 'D:\ccdi\ccdi\sql\ccdi_asset_info.sql'`
|
||||
Expected: 能看到完整建表语句与索引定义
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add sql/ccdi_asset_info.sql
|
||||
git commit -m "新增亲属资产表结构设计"
|
||||
```
|
||||
|
||||
### Task 2: 创建资产领域对象与映射接口
|
||||
|
||||
**Files:**
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\domain\CcdiAssetInfo.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\domain\dto\CcdiAssetInfoDTO.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\domain\vo\CcdiAssetInfoVO.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\domain\excel\CcdiAssetInfoExcel.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\domain\vo\AssetImportFailureVO.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\mapper\CcdiAssetInfoMapper.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\resources\mapper\info\collection\CcdiAssetInfoMapper.xml`
|
||||
|
||||
**Step 1: 创建领域实体与 DTO/VO**
|
||||
|
||||
- `CcdiAssetInfo` 映射表字段
|
||||
- `CcdiAssetInfoDTO` 承载亲属关系聚合保存时的子表数据
|
||||
- `CcdiAssetInfoVO` 用于详情回显
|
||||
|
||||
**Step 2: 创建 Excel 与失败记录对象**
|
||||
|
||||
- `CcdiAssetInfoExcel` 用于模板下载与导入解析
|
||||
- `AssetImportFailureVO` 仅返回失败记录
|
||||
|
||||
**Step 3: 定义 Mapper 能力**
|
||||
|
||||
- 按 `family_id + person_id` 查询资产列表
|
||||
- 按 `family_id + person_id` 删除资产
|
||||
- 批量插入资产
|
||||
- 导入场景下按亲属证件号查询归属员工候选
|
||||
|
||||
**Step 4: 运行编译检查**
|
||||
|
||||
Run: `mvn -pl ccdi-info-collection -am -DskipTests compile`
|
||||
Expected: 新增类与 XML 能被正常加载,无编译错误
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml
|
||||
git commit -m "新增亲属资产领域对象与映射"
|
||||
```
|
||||
|
||||
### Task 3: 实现资产服务与导入服务
|
||||
|
||||
**Files:**
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\service\ICcdiAssetInfoService.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\service\ICcdiAssetInfoImportService.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\service\impl\CcdiAssetInfoServiceImpl.java`
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\service\impl\CcdiAssetInfoImportServiceImpl.java`
|
||||
|
||||
**Step 1: 实现基础资产服务**
|
||||
|
||||
- 查询当前亲属资产列表
|
||||
- 按归属键删除资产
|
||||
- 批量保存资产
|
||||
- 过滤空行并校验必填字段、数值、日期
|
||||
|
||||
**Step 2: 实现资产导入异步服务**
|
||||
|
||||
- 初始化 Redis 状态
|
||||
- 解析 Excel 数据
|
||||
- 通过 `relation_cert_no` 反查亲属关系
|
||||
- 识别无法匹配和归属不唯一场景
|
||||
- 仅缓存失败记录
|
||||
|
||||
**Step 3: 明确 Redis Key 规则**
|
||||
|
||||
- 状态 key:`import:assetInfo:{taskId}`
|
||||
- 失败 key:`import:assetInfo:{taskId}:failures`
|
||||
|
||||
**Step 4: 运行编译检查**
|
||||
|
||||
Run: `mvn -pl ccdi-info-collection -am -DskipTests compile`
|
||||
Expected: 服务接口与实现均编译通过
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service
|
||||
git commit -m "实现亲属资产服务与导入服务"
|
||||
```
|
||||
|
||||
### Task 4: 扩展亲属关系 DTO 与 VO 聚合资产列表
|
||||
|
||||
**Files:**
|
||||
- Modify: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\domain\dto\CcdiStaffFmyRelationAddDTO.java`
|
||||
- Modify: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\domain\dto\CcdiStaffFmyRelationEditDTO.java`
|
||||
- Modify: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\domain\vo\CcdiStaffFmyRelationVO.java`
|
||||
|
||||
**Step 1: 为新增 DTO 增加 `assetInfoList`**
|
||||
|
||||
- 类型使用 `List<CcdiAssetInfoDTO>`
|
||||
- 保持与前端子表数据结构一致
|
||||
|
||||
**Step 2: 为编辑 DTO 增加 `assetInfoList`**
|
||||
|
||||
- 与新增 DTO 保持一致
|
||||
- 保留现有校验规则
|
||||
|
||||
**Step 3: 为详情 VO 增加 `assetInfoList`**
|
||||
|
||||
- 类型使用 `List<CcdiAssetInfoVO>`
|
||||
- 用于详情和编辑回显
|
||||
|
||||
**Step 4: 编译验证**
|
||||
|
||||
Run: `mvn -pl ccdi-info-collection -am -DskipTests compile`
|
||||
Expected: DTO、VO 依赖关系正常
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffFmyRelationAddDTO.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiStaffFmyRelationEditDTO.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiStaffFmyRelationVO.java
|
||||
git commit -m "扩展亲属关系聚合资产字段"
|
||||
```
|
||||
|
||||
### Task 5: 改造亲属关系服务聚合保存与详情查询
|
||||
|
||||
**Files:**
|
||||
- Modify: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\service\impl\CcdiStaffFmyRelationServiceImpl.java`
|
||||
- Check: `D:\ccdi\ccdi\ccdi-info-collection\src\main\resources\mapper\info\collection\CcdiStaffFmyRelationMapper.xml`
|
||||
|
||||
**Step 1: 注入资产服务**
|
||||
|
||||
- 在服务实现中注入 `ICcdiAssetInfoService`
|
||||
|
||||
**Step 2: 改造详情查询**
|
||||
|
||||
- 查询亲属关系主记录
|
||||
- 按 `personId + relationCertNo` 查询资产列表
|
||||
- 回填 `assetInfoList`
|
||||
|
||||
**Step 3: 改造新增逻辑**
|
||||
|
||||
- 保存亲属关系
|
||||
- 回填资产归属键
|
||||
- 批量保存资产
|
||||
|
||||
**Step 4: 改造编辑逻辑**
|
||||
|
||||
- 查询旧记录
|
||||
- 校验证件类型、证件号码未被修改
|
||||
- 更新主记录
|
||||
- 删除旧资产
|
||||
- 保存新资产列表
|
||||
|
||||
**Step 5: 改造删除逻辑**
|
||||
|
||||
- 根据待删 ID 先查询关系记录
|
||||
- 循环按 `family_id + person_id` 删除资产
|
||||
- 再批量删除亲属关系
|
||||
|
||||
**Step 6: 编译验证**
|
||||
|
||||
Run: `mvn -pl ccdi-info-collection -am -DskipTests compile`
|
||||
Expected: 亲属关系服务改造后可通过编译
|
||||
|
||||
**Step 7: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java
|
||||
git commit -m "改造亲属关系聚合保存亲属资产"
|
||||
```
|
||||
|
||||
### Task 6: 新增资产导入控制器并接入下载模板与状态查询
|
||||
|
||||
**Files:**
|
||||
- Create: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\controller\CcdiAssetInfoController.java`
|
||||
|
||||
**Step 1: 添加模板下载接口**
|
||||
|
||||
- `POST /ccdi/assetInfo/importTemplate`
|
||||
|
||||
**Step 2: 添加导入接口**
|
||||
|
||||
- `POST /ccdi/assetInfo/importData`
|
||||
- 解析 Excel 后提交异步任务
|
||||
|
||||
**Step 3: 添加状态与失败记录接口**
|
||||
|
||||
- `GET /ccdi/assetInfo/importStatus/{taskId}`
|
||||
- `GET /ccdi/assetInfo/importFailures/{taskId}`
|
||||
|
||||
**Step 4: 核对权限标识**
|
||||
|
||||
- 使用 `ccdi:staffFmyRelation:import` 还是新增独立权限
|
||||
- 若采用独立权限,记录需同步补菜单 SQL
|
||||
|
||||
**Step 5: 编译验证**
|
||||
|
||||
Run: `mvn -pl ccdi-info-collection -am -DskipTests compile`
|
||||
Expected: 控制器注册正常
|
||||
|
||||
**Step 6: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java
|
||||
git commit -m "新增亲属资产导入控制器"
|
||||
```
|
||||
|
||||
### Task 7: 补充亲属关系编辑防御校验
|
||||
|
||||
**Files:**
|
||||
- Modify: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\service\impl\CcdiStaffFmyRelationServiceImpl.java`
|
||||
|
||||
**Step 1: 查询旧记录**
|
||||
|
||||
- 编辑保存前按 `id` 查询旧关系
|
||||
|
||||
**Step 2: 比较证件类型与证件号码**
|
||||
|
||||
- 若值发生变化,抛出业务异常
|
||||
|
||||
**Step 3: 明确错误文案**
|
||||
|
||||
- 返回“关系人证件类型/证件号码不允许修改”
|
||||
|
||||
**Step 4: 编译验证**
|
||||
|
||||
Run: `mvn -pl ccdi-info-collection -am -DskipTests compile`
|
||||
Expected: 校验逻辑编译通过
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiStaffFmyRelationServiceImpl.java
|
||||
git commit -m "限制编辑亲属证件信息变更"
|
||||
```
|
||||
|
||||
### Task 8: 执行后端回归验证
|
||||
|
||||
**Files:**
|
||||
- Check: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\controller\CcdiStaffFmyRelationController.java`
|
||||
- Check: `D:\ccdi\ccdi\ccdi-info-collection\src\main\java\com\ruoyi\info\collection\controller\CcdiAssetInfoController.java`
|
||||
|
||||
**Step 1: 编译后端模块**
|
||||
|
||||
Run: `mvn -pl ccdi-info-collection -am clean compile`
|
||||
Expected: BUILD SUCCESS
|
||||
|
||||
**Step 2: 启动应用后手工验证接口**
|
||||
|
||||
Run: `mvn -pl ruoyi-admin -am spring-boot:run`
|
||||
Expected: 应用可正常启动
|
||||
|
||||
**Step 3: 验证关键场景**
|
||||
|
||||
- 查询亲属关系详情返回 `assetInfoList`
|
||||
- 新增/编辑亲属关系能保存资产
|
||||
- 删除亲属关系后资产被同步删除
|
||||
- 亲属资产导入可生成任务状态和失败记录
|
||||
|
||||
**Step 4: 整理验证记录**
|
||||
|
||||
- 将实际验证结果补充到开发记录或 PR 描述
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "完成亲属资产后端联调验证"
|
||||
```
|
||||
@@ -0,0 +1,136 @@
|
||||
# CCDI Docker 后端部署 Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 为 Spring Boot 后端与 `lsfx mock server` 建立可构建、可上传、可在服务器运行的 Docker 部署链路。
|
||||
|
||||
**Architecture:** 后端产物继续使用 Maven 构建出的 `ruoyi-admin.jar`,运行时通过 Java 21 容器加载 `local` profile。`lsfx mock server` 作为独立 Python 服务纳入仓库,并在 Compose 中与后端共享网络命名空间,以兼容现有 `http://localhost:8000` 配置。
|
||||
|
||||
**Tech Stack:** Maven, Spring Boot 3, Java 21, Docker Compose, Python 3.11, FastAPI, PowerShell, Paramiko
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 整理 `lsfx mock server` 到主仓库
|
||||
|
||||
**Files:**
|
||||
- Create: `lsfx-mock-server/**`
|
||||
- Modify: `docs/design/2026-03-13-ccdi-docker-deployment-design.md`
|
||||
- Test: `lsfx-mock-server/tests/test_api.py`
|
||||
|
||||
**Step 1: 复制并清理运行文件**
|
||||
|
||||
- 从现有工作树复制 `main.py`、`config/`、`models/`、`routers/`、`services/`、`utils/`、`requirements.txt`、`tests/`
|
||||
- 排除 `__pycache__`、`.pytest_cache`
|
||||
|
||||
**Step 2: 运行 mock server 测试**
|
||||
|
||||
Run: `python -m pytest lsfx-mock-server/tests -q`
|
||||
Expected: 测试通过,接口与健康检查可用
|
||||
|
||||
**Step 3: 修正最小必要问题**
|
||||
|
||||
- 若路径、依赖或导入失败,仅做最小修复
|
||||
|
||||
**Step 4: 记录目录用途**
|
||||
|
||||
- 在 `lsfx-mock-server/README.md` 补充与主项目集成的启动说明
|
||||
|
||||
### Task 2: 编写后端与 mock 的 Docker 文件
|
||||
|
||||
**Files:**
|
||||
- Create: `docker/backend/Dockerfile`
|
||||
- Create: `docker/mock/Dockerfile`
|
||||
- Modify: `lsfx-mock-server/README.md`
|
||||
|
||||
**Step 1: 创建后端镜像定义**
|
||||
|
||||
- 使用 Java 21 运行时镜像
|
||||
- 工作目录统一为 `/app`
|
||||
- 复制 `backend/ruoyi-admin.jar`
|
||||
- 默认入口使用 `java -jar /app/ruoyi-admin.jar`
|
||||
|
||||
**Step 2: 创建 mock 镜像定义**
|
||||
|
||||
- 使用 `python:3.11-slim`
|
||||
- 安装 `lsfx-mock-server/requirements.txt`
|
||||
- 启动 `python main.py`
|
||||
|
||||
**Step 3: 本地验证镜像定义**
|
||||
|
||||
Run: `docker build -f docker/mock/Dockerfile -t ccdi-lsfx-mock:test .`
|
||||
Expected: 构建成功
|
||||
|
||||
### Task 3: 编写 Compose 编排
|
||||
|
||||
**Files:**
|
||||
- Create: `docker-compose.yml`
|
||||
- Create: `.env.example`
|
||||
|
||||
**Step 1: 定义 `backend` 服务**
|
||||
|
||||
- 端口映射 `62318:8080`
|
||||
- 环境变量包含 `SPRING_PROFILES_ACTIVE=local` 与 `RUOYI_PROFILE=/app/data/ruoyi`
|
||||
- 卷挂载运行目录与日志目录
|
||||
|
||||
**Step 2: 定义 `lsfx-mock-server` 服务**
|
||||
|
||||
- 使用 `network_mode: "service:backend"`
|
||||
- 依赖 `backend`
|
||||
- 不额外对外暴露端口
|
||||
|
||||
**Step 3: 做配置校验**
|
||||
|
||||
Run: `docker compose config`
|
||||
Expected: Compose 文件能正常展开且无语法错误
|
||||
|
||||
### Task 4: 编写后端打包与远端部署脚本
|
||||
|
||||
**Files:**
|
||||
- Create: `deploy/deploy.ps1`
|
||||
- Create: `deploy/remote-deploy.py`
|
||||
|
||||
**Step 1: 编写本地打包流程**
|
||||
|
||||
- 执行 Maven 打包
|
||||
- 收集 `ruoyi-admin.jar`
|
||||
- 检查 `lsfx-mock-server` 运行文件完整性
|
||||
|
||||
**Step 2: 编写上传脚本**
|
||||
|
||||
- 使用 Paramiko 建立 SSH 与 SFTP 连接
|
||||
- 创建远端目录 `/volume1/webapp/ccdi`
|
||||
- 上传 Compose、Dockerfile、后端 JAR、mock 目录
|
||||
|
||||
**Step 3: 编写远端启动命令**
|
||||
|
||||
- 兼容 `docker compose` 与 `docker-compose`
|
||||
- 执行 `up -d --build`
|
||||
- 返回容器状态与后端日志摘要
|
||||
|
||||
### Task 5: 构建与联调验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/design/2026-03-13-ccdi-docker-deployment-design.md`
|
||||
|
||||
**Step 1: 本地构建后端**
|
||||
|
||||
Run: `mvn clean package -DskipTests`
|
||||
Expected: `ruoyi-admin/target/ruoyi-admin.jar` 生成成功
|
||||
|
||||
**Step 2: 本地跑通 Compose 校验**
|
||||
|
||||
Run: `docker compose config`
|
||||
Expected: 无错误
|
||||
|
||||
**Step 3: 远端部署验证**
|
||||
|
||||
- 验证 `backend` 容器启动
|
||||
- 验证 `mock server` 在后端网络命名空间内可访问
|
||||
- 验证 `http://116.62.17.81:62318/swagger-ui/index.html`
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add lsfx-mock-server docker docker-compose.yml .env.example deploy docs/design/2026-03-13-ccdi-docker-deployment-design.md docs/plans/backend/2026-03-13-ccdi-docker-deployment-backend-implementation.md docs/plans/frontend/2026-03-13-ccdi-docker-deployment-frontend-implementation.md
|
||||
git commit -m "新增Docker后端部署方案"
|
||||
```
|
||||
@@ -0,0 +1,69 @@
|
||||
# Deploy To NAS Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 为现有后端打包与远端部署链路增加一个可被 `.bat` 入口复用的 `DryRun` 模式。
|
||||
|
||||
**Architecture:** 保持 `deploy.ps1` 作为真实执行器不变,仅增加参数解析和轻量分支,让 BAT 可以先走快速验证,再走真实部署。底层上传与远端部署逻辑继续复用现有 Python 脚本。
|
||||
|
||||
**Tech Stack:** PowerShell, Python, Docker Compose, Windows CMD
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 为 `deploy.ps1` 增加 DryRun 模式
|
||||
|
||||
**Files:**
|
||||
- Modify: `deploy/deploy.ps1`
|
||||
- Test: `tests/deploy/test_deploy_to_nas.py`
|
||||
|
||||
**Step 1: 写失败测试**
|
||||
|
||||
```python
|
||||
def test_deploy_ps1_dry_run_prints_target():
|
||||
...
|
||||
```
|
||||
|
||||
**Step 2: 运行测试确认失败**
|
||||
|
||||
Run: `py -3.12 -m pytest tests/deploy/test_deploy_to_nas.py::test_deploy_ps1_dry_run_prints_target -q`
|
||||
Expected: 失败,因为 `deploy.ps1` 还不支持 `-DryRun`
|
||||
|
||||
**Step 3: 最小实现**
|
||||
|
||||
- 新增 `-DryRun` 开关
|
||||
- 打印 `Host/Port/Username/RemoteRoot`
|
||||
- 直接返回成功
|
||||
|
||||
**Step 4: 重新运行测试**
|
||||
|
||||
Run: `py -3.12 -m pytest tests/deploy/test_deploy_to_nas.py::test_deploy_ps1_dry_run_prints_target -q`
|
||||
Expected: 通过
|
||||
|
||||
### Task 2: 保持真实部署行为不变
|
||||
|
||||
**Files:**
|
||||
- Modify: `deploy/deploy.ps1`
|
||||
- Modify: `deploy/remote-deploy.py`
|
||||
- Test: `tests/deploy/test_deploy_to_nas.py`
|
||||
|
||||
**Step 1: 写失败测试**
|
||||
|
||||
```python
|
||||
def test_deploy_ps1_still_accepts_default_parameters():
|
||||
...
|
||||
```
|
||||
|
||||
**Step 2: 运行测试确认失败**
|
||||
|
||||
Run: `py -3.12 -m pytest tests/deploy/test_deploy_to_nas.py::test_deploy_ps1_still_accepts_default_parameters -q`
|
||||
Expected: 因缺少对应输出或参数处理失败而不通过
|
||||
|
||||
**Step 3: 最小实现**
|
||||
|
||||
- 保持默认 NAS 参数
|
||||
- 保持真实执行路径不变
|
||||
|
||||
**Step 4: 运行测试**
|
||||
|
||||
Run: `py -3.12 -m pytest tests/deploy/test_deploy_to_nas.py -q`
|
||||
Expected: 通过
|
||||
@@ -0,0 +1,253 @@
|
||||
# 员工资产导入与亲属资产导入拆分后端实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 将员工资产导入与亲属资产导入拆成两套独立后端接口与服务,确保员工页仅导入本人资产、亲属页仅导入亲属资产。
|
||||
|
||||
**Architecture:** 保留现有亲属资产导入控制器与服务作为“亲属专用导入链路”,新增一套员工资产专用导入控制器、Excel 模型、失败记录 VO 与异步导入服务。两套链路分别使用不同权限、模板和归属匹配规则,不再共享“本人/亲属兜底识别”逻辑。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, Redis, EasyExcel, JUnit 5, Mockito
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 固化拆分后的导入规则测试
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java`
|
||||
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java`
|
||||
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiAssetInfoControllerTest.java`
|
||||
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportControllerTest.java`
|
||||
|
||||
**Step 1: 写亲属资产导入失败测试**
|
||||
|
||||
- 为 `CcdiAssetInfoImportServiceImplTest` 增加用例:
|
||||
- 员工本人身份证号导入时失败
|
||||
- 失败文案为亲属资产专用文案
|
||||
- 为 `CcdiAssetInfoControllerTest` 校验模板标题为“亲属资产信息”
|
||||
|
||||
**Step 2: 运行测试确认当前失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiAssetInfoImportServiceImplTest,CcdiAssetInfoControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 新增断言失败
|
||||
- 失败原因是当前实现仍允许员工本人资产命中
|
||||
|
||||
**Step 3: 写员工资产导入测试**
|
||||
|
||||
- 新增 `CcdiBaseStaffAssetImportServiceImplTest`
|
||||
- 覆盖以下场景:
|
||||
- 员工本人身份证号导入成功
|
||||
- 亲属证件号导入失败
|
||||
- Redis key 使用员工资产独立前缀
|
||||
- 新增 `CcdiBaseStaffAssetImportControllerTest`
|
||||
- 覆盖模板标题、任务创建、状态与失败记录查询
|
||||
|
||||
**Step 4: 再次运行测试确认失败点准确**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiBaseStaffAssetImportServiceImplTest,CcdiBaseStaffAssetImportControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 因类与实现尚不存在而失败
|
||||
|
||||
**Step 5: 提交测试脚手架**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiAssetInfoImportServiceImplTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiBaseStaffAssetImportServiceImplTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiAssetInfoControllerTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportControllerTest.java
|
||||
git commit -m "补充资产导入拆分后端失败测试"
|
||||
```
|
||||
|
||||
### Task 2: 收敛亲属资产导入为亲属专用链路
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java`
|
||||
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml`
|
||||
|
||||
**Step 1: 最小实现修改**
|
||||
|
||||
- 将 `CcdiAssetInfoImportServiceImpl` 的归属匹配改为只调用 `selectOwnerCandidatesByRelationCertNos`
|
||||
- 删除员工本人兜底逻辑
|
||||
- 恢复亲属资产专用错误文案
|
||||
- 将 Excel 首列表头改为“亲属证件号*”
|
||||
- 将 controller 标题、swagger 文案、日志标题改回亲属资产专用表述
|
||||
- 权限仅保留 `ccdi:staffFmyRelation:import`
|
||||
|
||||
**Step 2: 运行亲属资产相关测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiAssetInfoImportServiceImplTest,CcdiAssetInfoControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 亲属资产测试全部通过
|
||||
|
||||
**Step 3: 检查无多余员工导入逻辑残留**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git grep -n "selectOwnerCandidatesByPersonIds|hasAnyPermi('ccdi:employee:import" -- "ccdi-info-collection/src/main/java/**/*.java" "ccdi-info-collection/src/main/resources/**/*.xml"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `CcdiAssetInfo*` 相关文件不再包含员工资产导入特有逻辑
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiAssetInfoController.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiAssetInfoExcel.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/AssetImportFailureVO.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml
|
||||
git commit -m "收敛亲属资产导入为亲属专用逻辑"
|
||||
```
|
||||
|
||||
### Task 3: 新增员工资产导入后端接口与模型
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportController.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffAssetImportService.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiBaseStaffAssetInfoExcel.java`
|
||||
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/BaseStaffAssetImportFailureVO.java`
|
||||
|
||||
**Step 1: 写最小接口定义**
|
||||
|
||||
- 定义员工资产导入 service 接口:
|
||||
- `importAssetInfo`
|
||||
- `importAssetInfoAsync`
|
||||
- `getImportStatus`
|
||||
- `getImportFailures`
|
||||
|
||||
**Step 2: 写 controller 最小实现**
|
||||
|
||||
- 提供以下接口:
|
||||
- `POST /ccdi/baseStaff/asset/importTemplate`
|
||||
- `POST /ccdi/baseStaff/asset/importData`
|
||||
- `GET /ccdi/baseStaff/asset/importStatus/{taskId}`
|
||||
- `GET /ccdi/baseStaff/asset/importFailures/{taskId}`
|
||||
- 权限使用 `ccdi:employee:import`
|
||||
|
||||
**Step 3: 写 Excel 与失败记录模型**
|
||||
|
||||
- `CcdiBaseStaffAssetInfoExcel` 首列使用“员工身份证号*”
|
||||
- `BaseStaffAssetImportFailureVO` 字段与员工资产模板保持一致
|
||||
|
||||
**Step 4: 运行员工资产 controller 测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiBaseStaffAssetImportControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- controller 测试通过
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffAssetImportController.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiBaseStaffAssetImportService.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiBaseStaffAssetInfoExcel.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/BaseStaffAssetImportFailureVO.java
|
||||
git commit -m "新增员工资产导入后端接口"
|
||||
```
|
||||
|
||||
### Task 4: 实现员工资产归属匹配与异步导入
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java`
|
||||
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java`
|
||||
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml`
|
||||
|
||||
**Step 1: 写最小匹配实现**
|
||||
|
||||
- 在 mapper 中新增 `selectOwnerCandidatesByBaseStaffIdCards`
|
||||
- SQL 只查询 `ccdi_base_staff.id_card`
|
||||
- service 只按员工身份证号匹配
|
||||
- 不查亲属关系表
|
||||
|
||||
**Step 2: 写导入成功逻辑**
|
||||
|
||||
- 复制 Excel 到 `CcdiAssetInfo`
|
||||
- 强制 `familyId = personId = 员工身份证号`
|
||||
- 使用独立 Redis 前缀,例如 `import:baseStaffAsset:`
|
||||
|
||||
**Step 3: 写失败逻辑**
|
||||
|
||||
- 未命中员工表时报错
|
||||
- 失败文案使用员工资产专用文案
|
||||
|
||||
**Step 4: 运行员工资产 service 测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiBaseStaffAssetImportServiceImplTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 员工资产 service 测试通过
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiBaseStaffAssetImportServiceImpl.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiAssetInfoMapper.java ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAssetInfoMapper.xml
|
||||
git commit -m "实现员工资产导入归属匹配"
|
||||
```
|
||||
|
||||
### Task 5: 执行回归验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/design/2026-03-13-employee-family-asset-import-split-design.md`
|
||||
|
||||
**Step 1: 运行后端定向测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-info-collection -am "-Dtest=CcdiAssetInfoImportServiceImplTest,CcdiAssetInfoControllerTest,CcdiBaseStaffAssetImportServiceImplTest,CcdiBaseStaffAssetImportControllerTest" "-Dsurefire.failIfNoSpecifiedTests=false"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 相关测试全部通过
|
||||
|
||||
**Step 2: 做源码断言检查**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git grep -n "/ccdi/baseStaff/asset|/ccdi/assetInfo" -- "ccdi-info-collection/src/main/java/**/*.java"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 员工与亲属两套接口都存在
|
||||
- 路由职责清晰
|
||||
|
||||
**Step 3: 更新设计文档的实现状态说明**
|
||||
|
||||
- 在设计文档末尾补充“已完成实现验证”的简短说明
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/design/2026-03-13-employee-family-asset-import-split-design.md
|
||||
git commit -m "完成资产导入拆分后端验证"
|
||||
```
|
||||
@@ -0,0 +1,149 @@
|
||||
# Project 40 Large Transaction Data Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 为开发库 `project_id=40` 直接插入一批符合大额交易模型口径的银行流水,并完成 SQL 级命中验证。
|
||||
|
||||
**Architecture:** 通过独立 SQL 脚本管理测试数据生命周期,只操作 `ccdi_bank_statement`。脚本先清理项目 40 旧流水,再按既定身份、账户、日期和金额批量插入命中流水与少量噪声流水,最后执行核验 SQL 确认每个指标至少命中一次。
|
||||
|
||||
**Tech Stack:** MySQL 5.7, PowerShell, 项目现有 `ccdi_bank_statement` 表结构, `assets/大额交易.csv`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 固化设计与目标数据清单
|
||||
|
||||
**Files:**
|
||||
- Modify: `D:\ccdi\ccdi\docs\plans\2026-03-16-large-transaction-project40-design.md`
|
||||
- Create: `D:\ccdi\ccdi\assets\database\2026-03-16-project40-large-transaction-seed.sql`
|
||||
|
||||
**Step 1: 复核命中口径**
|
||||
|
||||
对照 `assets/大额交易.csv` 和库内默认参数,整理每个指标的命中条件、阈值和使用身份。
|
||||
|
||||
**Step 2: 写出目标数据清单**
|
||||
|
||||
在 SQL 脚本注释区列出每类指标对应的样本数量、证件号、金额范围、关键词和日期范围。
|
||||
|
||||
**Step 3: 自检唯一键策略**
|
||||
|
||||
确认每条插入记录的 `(project_id, LE_ACCOUNT_NO, ACCOUNTING_DATE_ID, AMOUNT_DR, AMOUNT_CR)` 组合唯一。
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/design/2026-03-16-large-transaction-project40-design.md assets/database/2026-03-16-project40-large-transaction-seed.sql
|
||||
git commit -m "文档: 补充项目40大额交易测试数据设计"
|
||||
```
|
||||
|
||||
### Task 2: 编写清理与插入 SQL
|
||||
|
||||
**Files:**
|
||||
- Modify: `D:\ccdi\ccdi\assets\database\2026-03-16-project40-large-transaction-seed.sql`
|
||||
|
||||
**Step 1: 写清理语句**
|
||||
|
||||
添加只针对 `project_id=40` 的删除语句:
|
||||
|
||||
```sql
|
||||
DELETE FROM ccdi_bank_statement
|
||||
WHERE project_id = 40;
|
||||
```
|
||||
|
||||
**Step 2: 编写最小插入块**
|
||||
|
||||
先写 1 到 2 条房车消费和税务支出的插入语句,确认字段完整:
|
||||
|
||||
```sql
|
||||
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 (...);
|
||||
```
|
||||
|
||||
**Step 3: 扩展全部指标数据**
|
||||
|
||||
补全单笔大额收入、累计收入、年流水超限、大额存现、多次存现、大额转账和噪声流水。
|
||||
|
||||
**Step 4: 运行脚本**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mysql --host=116.62.17.81 --user=root --password=*** --database=ccdi < assets/database/2026-03-16-project40-large-transaction-seed.sql
|
||||
```
|
||||
|
||||
Expected: 执行成功,无唯一键冲突。
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add assets/database/2026-03-16-project40-large-transaction-seed.sql
|
||||
git commit -m "数据: 生成项目40大额交易测试流水"
|
||||
```
|
||||
|
||||
### Task 3: 编写并执行核验 SQL
|
||||
|
||||
**Files:**
|
||||
- Modify: `D:\ccdi\ccdi\assets\database\2026-03-16-project40-large-transaction-seed.sql`
|
||||
- Create: `D:\ccdi\ccdi\docs\implementation-reports\2026-03-16-project40-large-transaction-report.md`
|
||||
|
||||
**Step 1: 添加核验查询**
|
||||
|
||||
在脚本末尾添加按 `大额交易.csv` 口径改写后的项目 40 验证查询,覆盖全部指标。
|
||||
|
||||
**Step 2: 执行核验**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mysql --host=116.62.17.81 --user=root --password=*** --database=ccdi -e "/* verify queries */"
|
||||
```
|
||||
|
||||
Expected: 每个指标返回至少 1 条命中记录或 1 个命中分组。
|
||||
|
||||
**Step 3: 记录结果**
|
||||
|
||||
把每个指标的命中数量、示例流水编号和涉及人员写入报告文档。
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add assets/database/2026-03-16-project40-large-transaction-seed.sql docs/reports/implementation2026-03-16-project40-large-transaction-report.md
|
||||
git commit -m "验证: 完成项目40大额交易测试流水校验"
|
||||
```
|
||||
|
||||
### Task 4: 回归检查与收尾
|
||||
|
||||
**Files:**
|
||||
- Modify: `D:\ccdi\ccdi\docs\implementation-reports\2026-03-16-project40-large-transaction-report.md`
|
||||
|
||||
**Step 1: 检查项目总量**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mysql --host=116.62.17.81 --user=root --password=*** --database=ccdi -e "SELECT project_id, COUNT(*) FROM ccdi_bank_statement WHERE project_id=40 GROUP BY project_id;"
|
||||
```
|
||||
|
||||
Expected: `project_id=40` 存在稳定数量的测试流水。
|
||||
|
||||
**Step 2: 抽样检查页面关键字段**
|
||||
|
||||
确认 `TRX_DATE`、`USER_MEMO`、`CUSTOMER_ACCOUNT_NAME`、`AMOUNT_DR`、`AMOUNT_CR` 等字段适合前端展示。
|
||||
|
||||
**Step 3: 补充最终说明**
|
||||
|
||||
在报告中注明依赖的默认阈值、复用的测试身份和复跑方式。
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/reports/implementation2026-03-16-project40-large-transaction-report.md
|
||||
git commit -m "文档: 完善项目40大额交易测试流水报告"
|
||||
```
|
||||
@@ -0,0 +1,357 @@
|
||||
# Model Param CSV Alignment Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Align system default model parameters with `assets/模型默认参数.csv` on the backend and database side without changing the existing `listAll/saveAll` API contract.
|
||||
|
||||
**Architecture:** Keep `ccdi_model_param` as the single source of truth for model metadata and default values. Update initialization SQL, add an upgrade SQL for existing environments, and make a small service/mapper cleanup so query ordering and default-project copy behavior remain stable while historical `custom` projects stay untouched.
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MySQL, Maven
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 固化 CSV 对应的系统默认参数清单
|
||||
|
||||
**Files:**
|
||||
- Reference: `assets/模型默认参数.csv`
|
||||
- Modify: `sql/ccdi_model_param.sql`
|
||||
|
||||
**Step 1: 对照 CSV 列出最终参数集合**
|
||||
|
||||
确认最终系统默认参数为 5 个模型、16 个参数:
|
||||
|
||||
- `LARGE_TRANSACTION`
|
||||
- `SUSPICIOUS_PART_TIME`
|
||||
- `SUSPICIOUS_FOREIGN_EXCHANGE`
|
||||
- `ABNORMAL_BEHAVIOR`
|
||||
- `SUSPICIOUS_GAMBLING`
|
||||
|
||||
并确认每条记录的 `model_code`、`model_name`、`param_code`、`param_name`、`param_desc`、`param_value`、`param_unit`、`sort_order`。
|
||||
|
||||
**Step 2: 写一个失败前检查**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
Get-Content -Raw 'sql/ccdi_model_param.sql'
|
||||
```
|
||||
|
||||
预期:可以看到旧参数定义仍与 CSV 不完全一致,包含已废弃参数或缺失模型。
|
||||
|
||||
**Step 3: 更新初始化 SQL**
|
||||
|
||||
将 `sql/ccdi_model_param.sql` 中 `project_id = 0` 的初始化数据改为与 CSV 一致,要求:
|
||||
|
||||
- 删除旧的废弃参数
|
||||
- 新增 `ABNORMAL_BEHAVIOR`、`SUSPICIOUS_GAMBLING`
|
||||
- 保证 `sort_order` 从 1 开始递增
|
||||
- 不引入任何千分位格式数据
|
||||
|
||||
**Step 4: 做静态自检**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
Get-Content -Raw 'sql/ccdi_model_param.sql'
|
||||
```
|
||||
|
||||
预期:SQL 中只剩 CSV 对应的 16 条系统默认参数记录。
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add sql/ccdi_model_param.sql
|
||||
git commit -m "feat: 对齐模型默认参数初始化脚本"
|
||||
```
|
||||
|
||||
### Task 2: 为已有环境补充默认参数覆盖脚本
|
||||
|
||||
**Files:**
|
||||
- Create or Modify: `sql/2026-03-16-update-ccdi-model-param-defaults.sql`
|
||||
|
||||
**Step 1: 写覆盖脚本骨架**
|
||||
|
||||
脚本要求:
|
||||
|
||||
- 以事务包裹
|
||||
- 只处理 `project_id = 0`
|
||||
- 先删除旧的系统默认参数
|
||||
- 再插入与 CSV 一致的 16 条新默认参数
|
||||
|
||||
**Step 2: 写失败前检查**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
Test-Path 'sql/2026-03-16-update-ccdi-model-param-defaults.sql'
|
||||
Get-Content -Raw 'sql/2026-03-16-update-ccdi-model-param-defaults.sql'
|
||||
```
|
||||
|
||||
预期:若文件已存在,内容可能未完全符合最终参数集合;若不存在,则本步骤补齐。
|
||||
|
||||
**Step 3: 写最小正确实现**
|
||||
|
||||
确保脚本满足:
|
||||
|
||||
- `START TRANSACTION;`
|
||||
- `DELETE FROM ccdi_model_param WHERE project_id = 0;`
|
||||
- 插入 16 条目标数据
|
||||
- `COMMIT;`
|
||||
|
||||
**Step 4: 自检**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
Get-Content -Raw 'sql/2026-03-16-update-ccdi-model-param-defaults.sql'
|
||||
```
|
||||
|
||||
预期:脚本与初始化 SQL 的系统默认参数集合完全一致。
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add sql/2026-03-16-update-ccdi-model-param-defaults.sql
|
||||
git commit -m "feat: 新增模型默认参数升级脚本"
|
||||
```
|
||||
|
||||
### Task 3: 稳定后端查询顺序
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml`
|
||||
|
||||
**Step 1: 先查看当前 SQL**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
Get-Content -Raw 'ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml'
|
||||
```
|
||||
|
||||
预期:`selectByProjectId` 已存在,但排序可能仅依赖 `model_code, sort_order`。
|
||||
|
||||
**Step 2: 写一个小改动**
|
||||
|
||||
将 `selectByProjectId` 的排序改为稳定排序:
|
||||
|
||||
```xml
|
||||
ORDER BY model_code, sort_order, id
|
||||
```
|
||||
|
||||
如有必要,也检查 `selectByProjectAndModel` 是否需要补 `id` 兜底排序。
|
||||
|
||||
**Step 3: 静态验证**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
Get-Content -Raw 'ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml'
|
||||
```
|
||||
|
||||
预期:查询排序稳定,不依赖数据库默认返回顺序。
|
||||
|
||||
**Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiModelParamMapper.xml
|
||||
git commit -m "fix: 稳定模型参数查询顺序"
|
||||
```
|
||||
|
||||
### Task 4: 清理服务层的 CSV 对齐边界
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java`
|
||||
|
||||
**Step 1: 查看当前实现**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
Get-Content -Raw 'ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java'
|
||||
```
|
||||
|
||||
预期:当前已经支持 `listAll/saveAll`,但需要确认注释、排序依赖和默认项目复制逻辑是否与本次设计一致。
|
||||
|
||||
**Step 2: 写最小修正**
|
||||
|
||||
仅做与本次设计直接相关的修正:
|
||||
|
||||
- 保持 `projectId=0/default/custom` 的现有读取规则
|
||||
- 保持默认项目首次保存时复制系统默认参数全集
|
||||
- 不增加任何历史 `custom` 项目补齐逻辑
|
||||
- 不引入任何千分位处理逻辑
|
||||
- 如有重复或无效的局部变量,顺手清理为更直接的实现
|
||||
|
||||
**Step 3: 写失败前验证**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -am -DskipTests compile
|
||||
```
|
||||
|
||||
预期:若存在语法或导入问题,本步先暴露出来。
|
||||
|
||||
**Step 4: 调整到编译通过**
|
||||
|
||||
修正编译问题后再次运行:
|
||||
|
||||
```bash
|
||||
mvn -pl ccdi-project -am -DskipTests compile
|
||||
```
|
||||
|
||||
预期:BUILD SUCCESS。
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java
|
||||
git commit -m "refactor: 收敛模型参数服务对齐逻辑"
|
||||
```
|
||||
|
||||
### Task 5: 验证新旧环境脚本产物一致
|
||||
|
||||
**Files:**
|
||||
- Reference: `sql/ccdi_model_param.sql`
|
||||
- Reference: `sql/2026-03-16-update-ccdi-model-param-defaults.sql`
|
||||
- Optional Record: `docs/tests/records/model-param-backend-alignment-test.md`
|
||||
|
||||
**Step 1: 准备校验项**
|
||||
|
||||
至少检查:
|
||||
|
||||
- 模型总数是否为 5
|
||||
- 参数总数是否为 16
|
||||
- 每个 `model_code + param_code` 唯一
|
||||
- 所有 `param_value` 为原始字符串,不含千分位逗号
|
||||
|
||||
**Step 2: 执行静态比对**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
Get-Content -Raw 'sql/ccdi_model_param.sql'
|
||||
Get-Content -Raw 'sql/2026-03-16-update-ccdi-model-param-defaults.sql'
|
||||
```
|
||||
|
||||
预期:两个脚本中的系统默认参数集合一致。
|
||||
|
||||
**Step 3: 如本地有数据库,执行 SQL 验证**
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
SELECT model_code, COUNT(*) AS cnt
|
||||
FROM ccdi_model_param
|
||||
WHERE project_id = 0
|
||||
GROUP BY model_code
|
||||
ORDER BY model_code;
|
||||
```
|
||||
|
||||
```sql
|
||||
SELECT model_code, param_code, param_value
|
||||
FROM ccdi_model_param
|
||||
WHERE project_id = 0
|
||||
ORDER BY model_code, sort_order, id;
|
||||
```
|
||||
|
||||
预期:返回结果与 CSV 一致。
|
||||
|
||||
**Step 4: 记录结果**
|
||||
|
||||
将验证过程写入:
|
||||
|
||||
```text
|
||||
docs/tests/records/model-param-backend-alignment-test.md
|
||||
```
|
||||
|
||||
**Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git add docs/tests/records/model-param-backend-alignment-test.md
|
||||
git commit -m "test: 记录模型默认参数后端对齐验证"
|
||||
```
|
||||
|
||||
### Task 6: 验证接口行为不变
|
||||
|
||||
**Files:**
|
||||
- Reference: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiModelParamController.java`
|
||||
- Reference: `ruoyi-ui/src/api/ccdi/modelParam.js`
|
||||
|
||||
**Step 1: 启动后端**
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-admin -am spring-boot:run
|
||||
```
|
||||
|
||||
**Step 2: 验证查询接口**
|
||||
|
||||
使用 Swagger 或 HTTP 工具请求:
|
||||
|
||||
```http
|
||||
GET /ccdi/modelParam/listAll?projectId=0
|
||||
```
|
||||
|
||||
预期:
|
||||
|
||||
- 返回 `models` 数组
|
||||
- 包含 5 个模型
|
||||
- 各模型参数与 CSV 一致
|
||||
|
||||
**Step 3: 验证默认项目查询**
|
||||
|
||||
对一个 `configType=default` 项目请求:
|
||||
|
||||
```http
|
||||
GET /ccdi/modelParam/listAll?projectId=<default项目ID>
|
||||
```
|
||||
|
||||
预期:返回系统默认参数全集。
|
||||
|
||||
**Step 4: 验证默认项目首次保存**
|
||||
|
||||
调用:
|
||||
|
||||
```http
|
||||
POST /ccdi/modelParam/saveAll
|
||||
```
|
||||
|
||||
请求体示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"projectId": 123,
|
||||
"models": [
|
||||
{
|
||||
"modelCode": "LARGE_TRANSACTION",
|
||||
"params": [
|
||||
{
|
||||
"paramCode": "SINGLE_TRANSACTION_AMOUNT",
|
||||
"paramValue": "2222"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
预期:
|
||||
|
||||
- 保存成功
|
||||
- 该项目 `config_type` 变为 `custom`
|
||||
- 项目参数表中只复制当前系统默认参数全集,不补历史 `custom` 项目
|
||||
|
||||
**Step 5: 停止后端进程并提交**
|
||||
|
||||
测试结束后关闭 `mvn spring-boot:run` 启动的进程,再提交测试记录:
|
||||
|
||||
```bash
|
||||
git add docs/tests/records/model-param-backend-alignment-test.md
|
||||
git commit -m "test: 完成模型参数后端接口回归验证"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Plan complete and saved to `docs/plans/backend/2026-03-16-model-param-csv-alignment-backend-implementation.md`.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Param Save Bar Fixed Bottom Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 评估并确认模型参数页底部保存栏悬浮需求是否需要后端配合,并输出后端执行结论。
|
||||
|
||||
**Architecture:** 本次需求仅涉及前端页面布局与样式调整,参数查询和保存接口、返回结构、DTO 以及持久化逻辑均不发生变化。后端实施计划以确认边界和回归验证要点为主,不引入代码修改。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, 若依后端接口, 现有模型参数保存 API
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 确认需求边界
|
||||
|
||||
**Files:**
|
||||
- Review: `docs/design/2026-03-16-param-save-bar-fixed-bottom-design.md`
|
||||
- Review: `ccdi-project`
|
||||
- Review: `ccdi-info-collection`
|
||||
|
||||
**Step 1: 阅读设计文档并确认影响面**
|
||||
|
||||
检查设计文档中关于“仅修改前端布局”的描述,确认没有新增字段、接口或保存时机变更。
|
||||
|
||||
**Step 2: 对照现有接口职责**
|
||||
|
||||
确认 `listAllParams` 和 `saveAllParams` 相关接口仍可满足前端使用,不需要新增响应字段或调整 DTO。
|
||||
|
||||
**Step 3: 记录实施结论**
|
||||
|
||||
结论应明确写为:后端无需改动,仅需配合前端回归验证现有保存流程。
|
||||
|
||||
### Task 2: 回归验证清单
|
||||
|
||||
**Files:**
|
||||
- Review: `docs/design/2026-03-16-param-save-bar-fixed-bottom-design.md`
|
||||
|
||||
**Step 1: 验证参数查询接口**
|
||||
|
||||
确保页面布局调整后,前端仍以原方式调用查询接口,接口入参与返回结构保持不变。
|
||||
|
||||
**Step 2: 验证批量保存接口**
|
||||
|
||||
确保前端仅改变按钮展示位置,不改变 `saveDTO` 结构与保存触发方式。
|
||||
|
||||
**Step 3: 记录无需联调变更**
|
||||
|
||||
在实施说明中注明:后端只需参与已有保存链路的回归确认,不需要代码发布。
|
||||
@@ -0,0 +1,760 @@
|
||||
# Project Bank Statement Tagging Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 为项目流水新增自动打标与手动重算后端能力,支持批量上传和拉取本行信息两条链路自动触发,支持项目级互斥重算与规则级并行执行。
|
||||
|
||||
**Architecture:** 在 `ccdi-project` 中新增标签规则表、结果表、任务表及对应 Mapper;引入项目级重算协调器和规则级线程池;在 `CcdiFileUploadServiceImpl` 的批量上传与拉取本行信息收尾阶段统一申请项目级标签重算;通过独立的标签服务读取规则元数据、参数配置,调度 Mapper XML 中的规则 SQL 并写入结果表。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, JUnit 5, Mockito, Maven
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 定义手动重算接口契约
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiBankTagRebuildDTO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankTagService.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增控制器测试,验证接口会把 `projectId + modelCode` 透传到 Service:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void rebuild_shouldDelegateProjectAndModelCode() {
|
||||
CcdiBankTagRebuildDTO dto = new CcdiBankTagRebuildDTO();
|
||||
dto.setProjectId(40L);
|
||||
dto.setModelCode("LARGE_TRANSACTION");
|
||||
|
||||
when(bankTagService.submitRebuild(dto, "admin")).thenReturn("标签重算任务已提交");
|
||||
|
||||
try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
|
||||
mocked.when(SecurityUtils::getUsername).thenReturn("admin");
|
||||
|
||||
AjaxResult result = controller.rebuild(dto);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
verify(bankTagService).submitRebuild(dto, "admin");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest#rebuild_shouldDelegateProjectAndModelCode
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 DTO、Controller 或 Service 契约尚不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
补最小接口:
|
||||
|
||||
```java
|
||||
public interface ICcdiBankTagService {
|
||||
String submitRebuild(CcdiBankTagRebuildDTO dto, String operator);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@PostMapping("/rebuild")
|
||||
public AjaxResult rebuild(@Validated @RequestBody CcdiBankTagRebuildDTO dto) {
|
||||
String operator = SecurityUtils.getUsername();
|
||||
return AjaxResult.success(bankTagService.submitRebuild(dto, operator));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest#rebuild_shouldDelegateProjectAndModelCode
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiBankTagRebuildDTO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankTagService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java
|
||||
git commit -m "test: 补充流水标签重算接口契约"
|
||||
```
|
||||
|
||||
### Task 2: 新增标签核心表结构与实体映射
|
||||
|
||||
**Files:**
|
||||
- Create: `sql/2026-03-16-bank-tagging.sql`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagRule.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagResult.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagTask.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagEntityMappingTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增一个轻量测试,校验结果实体的关键字段存在:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void bankTagResult_shouldExposeStatementAndObjectFields() {
|
||||
CcdiBankTagResult result = new CcdiBankTagResult();
|
||||
result.setProjectId(40L);
|
||||
result.setRuleCode("RULE_1");
|
||||
result.setBankStatementId(10L);
|
||||
result.setGroupId(40);
|
||||
result.setLogId(40001);
|
||||
result.setObjectType("STAFF_ID_CARD");
|
||||
result.setObjectKey("330101198801010011");
|
||||
|
||||
assertEquals(40L, result.getProjectId());
|
||||
assertEquals(40001, result.getLogId());
|
||||
assertEquals("STAFF_ID_CARD", result.getObjectType());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagEntityMappingTest#bankTagResult_shouldExposeStatementAndObjectFields
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是新实体尚不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
- 新建三张表的 SQL 脚本
|
||||
- 新建三个实体类,字段只覆盖第一版设计所需字段
|
||||
- 规则表初始化“大额交易” 8 条规则元数据
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagEntityMappingTest#bankTagResult_shouldExposeStatementAndObjectFields
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add sql/2026-03-16-bank-tagging.sql ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagRule.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagResult.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagTask.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagEntityMappingTest.java
|
||||
git commit -m "feat: 新增流水标签核心表结构与实体映射"
|
||||
```
|
||||
|
||||
### Task 3: 建立规则元数据与结果表 Mapper
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagRuleMapper.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapper.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagTaskMapper.java`
|
||||
- Create: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagRuleMapper.xml`
|
||||
- Create: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagResultMapper.xml`
|
||||
- Create: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagTaskMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapperXmlTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增 XML 渲染测试,校验结果表支持按项目或项目+模型删除:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void deleteByProjectAndOptionalModel_shouldRenderScopedDelete() throws Exception {
|
||||
String xml = readXml("mapper/ccdi/project/CcdiBankTagResultMapper.xml");
|
||||
assertTrue(xml.contains("delete from ccdi_bank_statement_tag_result"));
|
||||
assertTrue(xml.contains("project_id = #{projectId}"));
|
||||
assertTrue(xml.contains("model_code = #{modelCode}"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagResultMapperXmlTest#deleteByProjectAndOptionalModel_shouldRenderScopedDelete
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 Mapper XML 尚不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
- 规则表 Mapper 提供启用规则查询
|
||||
- 结果表 Mapper 提供批量插入和按范围删除
|
||||
- 任务表 Mapper 提供创建任务、更新任务、查询运行中任务
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagResultMapperXmlTest#deleteByProjectAndOptionalModel_shouldRenderScopedDelete
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagRuleMapper.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapper.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagTaskMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagRuleMapper.xml ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagResultMapper.xml ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagTaskMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapperXmlTest.java
|
||||
git commit -m "feat: 新增流水标签规则结果任务Mapper"
|
||||
```
|
||||
|
||||
### Task 4: 为规则 SQL 定义统一返回 VO
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagStatementHitVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagObjectHitVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java`
|
||||
- Create: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增 XML 测试,先校验流水级命中 SQL 会返回 `group_id` 和 `log_id`:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void statementRuleSql_shouldSelectGroupIdAndLogId() throws Exception {
|
||||
String xml = readXml("mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml");
|
||||
assertTrue(xml.contains("AS groupId"));
|
||||
assertTrue(xml.contains("AS logId"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest#statementRuleSql_shouldSelectGroupIdAndLogId
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
先定义统一 VO 和一个最小的 XML 框架,确保:
|
||||
|
||||
- 流水级规则 SQL 返回 `bankStatementId`、`groupId`、`logId`、`reasonDetail`
|
||||
- 对象级规则 SQL 返回 `objectType`、`objectKey`、`reasonDetail`
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest#statementRuleSql_shouldSelectGroupIdAndLogId
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagStatementHitVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagObjectHitVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java
|
||||
git commit -m "feat: 定义流水标签规则命中返回结构"
|
||||
```
|
||||
|
||||
### Task 5: 先实现一条流水级规则的失败测试与最小通过
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
以“房车消费支出交易”为第一条规则,测试 XML 中存在 `selectHouseOrCarExpenseStatements`:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void houseOrCarExpenseRule_shouldJoinBankStatementAndReturnStatementHitFields() throws Exception {
|
||||
String xml = readXml("mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml");
|
||||
assertTrue(xml.contains("selectHouseOrCarExpenseStatements"));
|
||||
assertTrue(xml.contains("bs.bank_statement_id AS bankStatementId"));
|
||||
assertTrue(xml.contains("bs.group_id AS groupId"));
|
||||
assertTrue(xml.contains("bs.batch_id AS logId"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest#houseOrCarExpenseRule_shouldJoinBankStatementAndReturnStatementHitFields
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 XML 中补第一条规则 SQL,并返回固定原因摘要。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest#houseOrCarExpenseRule_shouldJoinBankStatementAndReturnStatementHitFields
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java
|
||||
git commit -m "test: 打通首条流水标签规则SQL"
|
||||
```
|
||||
|
||||
### Task 6: 完成剩余 7 条规则 SQL
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
为剩余规则补 XML 存在性断言,分别覆盖:
|
||||
|
||||
- `selectTaxExpenseStatements`
|
||||
- `selectSingleLargeIncomeStatements`
|
||||
- `selectCumulativeIncomeObjects`
|
||||
- `selectAnnualTurnoverObjects`
|
||||
- `selectLargeCashDepositStatements`
|
||||
- `selectFrequentCashDepositObjects`
|
||||
- `selectLargeTransferStatements`
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
补齐 7 条规则 SQL,要求:
|
||||
|
||||
- 阈值类规则使用入参,不在 XML 中写死参数值
|
||||
- 流水级规则返回 `bankStatementId/groupId/logId/reasonDetail`
|
||||
- 对象级规则返回 `objectType/objectKey/reasonDetail`
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java
|
||||
git commit -m "feat: 补齐大额交易全部标签规则SQL"
|
||||
```
|
||||
|
||||
### Task 7: 实现项目参数读取与规则注册表
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagRuleExecutionConfig.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增测试,验证 resolver 会根据项目读取有效参数,并为规则产出执行配置:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void resolve_shouldReadEffectiveProjectParamsForThresholdRules() {
|
||||
BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);
|
||||
assertEquals("1111", config.getThresholdValue("SINGLE_TRANSACTION_AMOUNT"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=BankTagRuleConfigResolverTest#resolve_shouldReadEffectiveProjectParamsForThresholdRules
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
实现 resolver:
|
||||
|
||||
- 读取项目 `configType`
|
||||
- 选择有效 `projectId`
|
||||
- 读取模型参数
|
||||
- 按 `ruleCode` 组装执行配置
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=BankTagRuleConfigResolverTest#resolve_shouldReadEffectiveProjectParamsForThresholdRules
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagRuleExecutionConfig.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java
|
||||
git commit -m "feat: 新增流水标签规则执行参数解析器"
|
||||
```
|
||||
|
||||
### Task 8: 实现规则级并行执行服务
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/config/BankTagThreadPoolConfig.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增服务测试,验证项目级任务会先删旧结果,再并行执行规则并汇总命中数:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void rebuildProject_shouldDeleteOldResultsBeforeSubmittingRuleTasks() {
|
||||
service.rebuildProject(40L, null, "admin", TriggerType.MANUAL);
|
||||
verify(resultMapper).deleteByProjectAndModel(40L, null);
|
||||
verify(resultMapper).insertBatch(anyList());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagServiceImplTest#rebuildProject_shouldDeleteOldResultsBeforeSubmittingRuleTasks
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
实现 `CcdiBankTagServiceImpl`:
|
||||
|
||||
- 查询启用规则
|
||||
- 删旧结果
|
||||
- 使用 `tagRuleExecutor` 并行提交每条规则
|
||||
- 汇总命中结果
|
||||
- 更新任务状态
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagServiceImplTest#rebuildProject_shouldDeleteOldResultsBeforeSubmittingRuleTasks
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/config/BankTagThreadPoolConfig.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java
|
||||
git commit -m "feat: 实现规则级并行流水标签重算服务"
|
||||
```
|
||||
|
||||
### Task 9: 实现同项目互斥与自动补跑协调器
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增测试,验证同一项目运行中时:
|
||||
|
||||
- 手动触发会被拒绝
|
||||
- 自动触发会标记 `need_rerun`
|
||||
|
||||
```java
|
||||
@Test
|
||||
void submitManualRebuild_shouldRejectWhenProjectAlreadyRunning() {
|
||||
assertThrows(ServiceException.class, () -> coordinator.submitManual(40L, null, "admin"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=ProjectBankTagRebuildCoordinatorTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
实现协调器:
|
||||
|
||||
- 使用 `projectId` 级别互斥
|
||||
- 自动触发遇到运行中任务时打 `need_rerun`
|
||||
- 当前任务结束后根据 `need_rerun` 触发补跑
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=ProjectBankTagRebuildCoordinatorTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java
|
||||
git commit -m "feat: 新增项目级流水标签重算互斥与补跑协调器"
|
||||
```
|
||||
|
||||
### Task 10: 接入批量上传收尾自动触发
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增测试,验证批量上传所有文件完成后会申请项目级重算:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void batchUploadCompletion_shouldSubmitProjectTagRebuildWhenAnyFileSucceeded() {
|
||||
verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_BATCH_UPLOAD);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#batchUploadCompletion_shouldSubmitProjectTagRebuildWhenAnyFileSucceeded
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
重构 `submitTasksAsync`:
|
||||
|
||||
- 收集每个文件任务的 `CompletableFuture`
|
||||
- `allOf(...).whenComplete(...)` 收尾
|
||||
- 至少一个文件成功落库时申请自动重算
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#batchUploadCompletion_shouldSubmitProjectTagRebuildWhenAnyFileSucceeded
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "feat: 接入批量上传完成后的自动流水打标"
|
||||
```
|
||||
|
||||
### Task 11: 接入拉取本行信息收尾自动触发
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增测试,验证拉取本行信息全部任务完成后也会申请项目级重算:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void pullBankInfoCompletion_shouldSubmitProjectTagRebuildWhenAnyTaskSucceeded() {
|
||||
verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_PULL_BANK_INFO);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#pullBankInfoCompletion_shouldSubmitProjectTagRebuildWhenAnyTaskSucceeded
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
仿照批量上传链路改造 `submitPullBankInfoTasks`,在全部任务结束后申请一次自动重算。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#pullBankInfoCompletion_shouldSubmitProjectTagRebuildWhenAnyTaskSucceeded
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "feat: 接入拉取本行信息完成后的自动流水打标"
|
||||
```
|
||||
|
||||
### Task 12: 完成全量验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/plans/backend/2026-03-16-project-bank-statement-tagging-backend-implementation.md`
|
||||
|
||||
**Step 1: Run focused backend tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest,CcdiBankTagEntityMappingTest,CcdiBankTagResultMapperXmlTest,CcdiBankTagAnalysisMapperXmlTest,BankTagRuleConfigResolverTest,CcdiBankTagServiceImplTest,ProjectBankTagRebuildCoordinatorTest,CcdiFileUploadServiceImplTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 所有新增测试通过
|
||||
|
||||
**Step 2: Run module compile**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn clean compile -pl ccdi-project -am
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `BUILD SUCCESS`
|
||||
|
||||
**Step 3: Update verification notes**
|
||||
|
||||
在本实施计划末尾补充实际执行结果摘要和任何遗留风险。
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project sql docs/plans/backend/2026-03-16-project-bank-statement-tagging-backend-implementation.md
|
||||
git commit -m "feat: 完成项目流水标签后端实现"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实际执行结果
|
||||
|
||||
### 已完成范围
|
||||
|
||||
- 已新增手动重算接口、标签规则/结果/任务三张核心表及实体映射
|
||||
- 已新增规则、结果、任务、分析四类 Mapper 与 XML
|
||||
- 已完成“大额交易” 8 条规则 SQL 的首版落地
|
||||
- 已完成规则参数解析器、规则级线程池、规则级并行重算服务
|
||||
- 已完成项目级互斥协调器与自动触发补跑标记逻辑
|
||||
- 已接入批量上传与拉取本行信息两条链路的批次收尾自动触发
|
||||
|
||||
### 实际验证命令
|
||||
|
||||
在 `ccdi-project` 模块目录执行:
|
||||
|
||||
```bash
|
||||
mvn test "-Dtest=CcdiBankTagControllerTest,CcdiBankTagEntityMappingTest,CcdiBankTagResultMapperXmlTest,CcdiBankTagAnalysisMapperXmlTest,BankTagRuleConfigResolverTest,CcdiBankTagServiceImplTest,ProjectBankTagRebuildCoordinatorTest,CcdiFileUploadServiceImplTest"
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
### 实际验证结果
|
||||
|
||||
- 聚焦测试集:`BUILD SUCCESS`
|
||||
- `ccdi-project` 模块编译:`BUILD SUCCESS`
|
||||
- 聚焦测试共执行 30 个测试,0 失败,0 错误
|
||||
|
||||
### 遗留风险
|
||||
|
||||
- 当前规则 SQL 已按设计稿和现有表结构落地,但尚未补集成测试验证真实数据库数据命中情况
|
||||
- 自动补跑已支持 `need_rerun` 标记与串行补跑,后续建议增加更完整的并发场景回归测试
|
||||
- 当前实施仅完成后端能力,结果查询接口与前端展示仍未接入
|
||||
@@ -0,0 +1,410 @@
|
||||
# Project Upload File Delete Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 为项目上传文件列表新增后端删除能力,支持删除已解析成功的文件、清理本地流水,并把上传记录状态更新为 `deleted`。
|
||||
|
||||
**Architecture:** 在 `CcdiFileUploadController` 新增按记录 ID 删除接口,由 Controller 获取当前登录用户 ID 并传给 `ICcdiFileUploadService`。Service 负责查询记录、校验状态、调用 `LsfxAnalysisClient.deleteFiles`、删除 `ccdi_bank_statement` 中对应 `logId` 的流水,并更新 `ccdi_file_upload_record.file_status` 为 `deleted`;统计 VO 同步扩展 `deleted` 状态。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, JUnit 5, Mockito, Maven
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 补齐删除接口控制器契约
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
在 `CcdiFileUploadControllerTest` 中新增测试,验证删除接口会读取当前登录用户 ID 并调用 Service:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void deleteFile_shouldUseCurrentLoginUserId() {
|
||||
try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
|
||||
mocked.when(SecurityUtils::getUserId).thenReturn(9527L);
|
||||
when(fileUploadService.deleteFileUploadRecord(123L, 9527L))
|
||||
.thenReturn("删除成功");
|
||||
|
||||
AjaxResult result = controller.deleteFile(123L);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
assertEquals("删除成功", result.get("msg"));
|
||||
verify(fileUploadService).deleteFileUploadRecord(123L, 9527L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadControllerTest#deleteFile_shouldUseCurrentLoginUserId
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 `CcdiFileUploadController` 中还没有 `deleteFile` 方法或 `ICcdiFileUploadService` 中还没有 `deleteFileUploadRecord` 方法
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在接口和控制器中补最小实现:
|
||||
|
||||
```java
|
||||
String deleteFileUploadRecord(Long id, Long operatorUserId);
|
||||
```
|
||||
|
||||
```java
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除上传文件", description = "按上传记录ID删除文件并清理流水")
|
||||
public AjaxResult deleteFile(@PathVariable Long id) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
String message = fileUploadService.deleteFileUploadRecord(id, userId);
|
||||
return AjaxResult.success(message);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadControllerTest#deleteFile_shouldUseCurrentLoginUserId
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java
|
||||
git commit -m "test: 补充上传文件删除接口控制器契约"
|
||||
```
|
||||
|
||||
### Task 2: 实现删除成功主链路
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
在 `CcdiFileUploadServiceImplTest` 中新增成功链路测试:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void deleteFileUploadRecord_shouldDeletePlatformFileBankStatementsAndMarkDeleted() {
|
||||
CcdiFileUploadRecord record = buildRecord();
|
||||
record.setProjectId(PROJECT_ID);
|
||||
record.setLsfxProjectId(LSFX_PROJECT_ID);
|
||||
record.setLogId(LOG_ID);
|
||||
record.setFileStatus("parsed_success");
|
||||
|
||||
when(recordMapper.selectById(RECORD_ID)).thenReturn(record);
|
||||
when(lsfxClient.deleteFiles(any())).thenReturn(buildDeleteFilesResponse());
|
||||
|
||||
String result = service.deleteFileUploadRecord(RECORD_ID, 9527L);
|
||||
|
||||
assertEquals("删除成功", result);
|
||||
verify(lsfxClient).deleteFiles(argThat(request ->
|
||||
request.getGroupId().equals(LSFX_PROJECT_ID)
|
||||
&& request.getUserId().equals(9527)
|
||||
&& request.getLogIds().length == 1
|
||||
&& request.getLogIds()[0].equals(LOG_ID)
|
||||
));
|
||||
verify(bankStatementMapper).deleteByProjectIdAndBatchId(PROJECT_ID, LOG_ID);
|
||||
verify(recordMapper).updateById(argThat(item ->
|
||||
RECORD_ID.equals(item.getId()) && "deleted".equals(item.getFileStatus())
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldDeletePlatformFileBankStatementsAndMarkDeleted
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 `CcdiFileUploadServiceImpl` 中还没有 `deleteFileUploadRecord` 实现
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 `CcdiFileUploadServiceImpl` 中实现删除主链路:
|
||||
|
||||
```java
|
||||
@Override
|
||||
@Transactional
|
||||
public String deleteFileUploadRecord(Long id, Long operatorUserId) {
|
||||
CcdiFileUploadRecord record = recordMapper.selectById(id);
|
||||
validateDeleteRecord(record);
|
||||
|
||||
DeleteFilesRequest request = new DeleteFilesRequest();
|
||||
request.setGroupId(record.getLsfxProjectId());
|
||||
request.setLogIds(new Integer[]{record.getLogId()});
|
||||
request.setUserId(toUploadUserId(operatorUserId));
|
||||
|
||||
DeleteFilesResponse response = lsfxClient.deleteFiles(request);
|
||||
if (response == null || Boolean.FALSE.equals(response.getSuccessResponse())) {
|
||||
throw new RuntimeException("流水分析平台删除文件失败");
|
||||
}
|
||||
|
||||
bankStatementMapper.deleteByProjectIdAndBatchId(record.getProjectId(), record.getLogId());
|
||||
|
||||
CcdiFileUploadRecord update = new CcdiFileUploadRecord();
|
||||
update.setId(record.getId());
|
||||
update.setFileStatus("deleted");
|
||||
recordMapper.updateById(update);
|
||||
return "删除成功";
|
||||
}
|
||||
```
|
||||
|
||||
同时补一个私有校验方法,仅先满足成功路径所需字段。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldDeletePlatformFileBankStatementsAndMarkDeleted
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "feat: 打通上传文件删除成功主链路"
|
||||
```
|
||||
|
||||
### Task 3: 补齐删除前置校验与失败保护
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
为关键边界新增失败测试,至少覆盖“状态不允许删除”和“平台删除失败时不应更新本地状态”:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void deleteFileUploadRecord_shouldRejectNonParsedSuccessStatus() {
|
||||
CcdiFileUploadRecord record = buildRecord();
|
||||
record.setFileStatus("parsed_failed");
|
||||
when(recordMapper.selectById(RECORD_ID)).thenReturn(record);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.deleteFileUploadRecord(RECORD_ID, 9527L));
|
||||
|
||||
assertTrue(exception.getMessage().contains("仅支持删除解析成功文件"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails() {
|
||||
CcdiFileUploadRecord record = buildRecord();
|
||||
record.setFileStatus("parsed_success");
|
||||
record.setLogId(LOG_ID);
|
||||
record.setLsfxProjectId(LSFX_PROJECT_ID);
|
||||
when(recordMapper.selectById(RECORD_ID)).thenReturn(record);
|
||||
when(lsfxClient.deleteFiles(any())).thenThrow(new RuntimeException("lsfx delete failed"));
|
||||
|
||||
assertThrows(RuntimeException.class, () -> service.deleteFileUploadRecord(RECORD_ID, 9527L));
|
||||
|
||||
verify(bankStatementMapper, org.mockito.Mockito.never()).deleteByProjectIdAndBatchId(any(), any());
|
||||
verify(recordMapper, org.mockito.Mockito.never()).updateById(argThat(item ->
|
||||
"deleted".equals(item.getFileStatus())
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldRejectNonParsedSuccessStatus,CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是现有实现还没有完整的前置校验和失败保护
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
补全 Service 校验和异常处理:
|
||||
|
||||
```java
|
||||
private void validateDeleteRecord(CcdiFileUploadRecord record) {
|
||||
if (record == null) {
|
||||
throw new RuntimeException("上传记录不存在");
|
||||
}
|
||||
if (!"parsed_success".equals(record.getFileStatus())) {
|
||||
if ("deleted".equals(record.getFileStatus())) {
|
||||
throw new RuntimeException("文件已删除,请勿重复操作");
|
||||
}
|
||||
throw new RuntimeException("仅支持删除解析成功文件");
|
||||
}
|
||||
if (record.getLsfxProjectId() == null) {
|
||||
throw new RuntimeException("缺少流水分析项目ID");
|
||||
}
|
||||
if (record.getLogId() == null) {
|
||||
throw new RuntimeException("缺少文件logId");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
不要吞掉平台删除异常,让事务直接回滚。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldRejectNonParsedSuccessStatus,CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "test: 补齐上传文件删除校验与失败保护"
|
||||
```
|
||||
|
||||
### Task 4: 扩展统计口径支持 `deleted`
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiFileUploadStatisticsVO.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增统计测试,验证 `deleted` 状态会被正确映射到 VO:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void countByStatus_shouldIncludeDeletedCount() {
|
||||
when(recordMapper.countByStatus(PROJECT_ID)).thenReturn(List.of(
|
||||
Map.of("status", "uploading", "count", 1),
|
||||
Map.of("status", "deleted", "count", 2)
|
||||
));
|
||||
|
||||
CcdiFileUploadStatisticsVO result = service.countByStatus(PROJECT_ID);
|
||||
|
||||
assertEquals(1L, result.getUploading());
|
||||
assertEquals(2L, result.getDeleted());
|
||||
assertEquals(3L, result.getTotal());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#countByStatus_shouldIncludeDeletedCount
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 `CcdiFileUploadStatisticsVO` 还没有 `deleted` 字段,或 `countByStatus` 还没有映射该状态
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 VO 和 Service 中补 `deleted` 字段及映射:
|
||||
|
||||
```java
|
||||
private Long deleted;
|
||||
```
|
||||
|
||||
```java
|
||||
vo.setDeleted(0L);
|
||||
case "deleted" -> vo.setDeleted(count);
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#countByStatus_shouldIncludeDeletedCount
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiFileUploadStatisticsVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "feat: 扩展上传文件统计支持已删除状态"
|
||||
```
|
||||
|
||||
### Task 5: 跑后端回归验证
|
||||
|
||||
**Files:**
|
||||
- Verify only: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
|
||||
- Verify only: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Verify only: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java`
|
||||
- Verify only: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadControllerTest,CcdiFileUploadServiceImplTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 全部 `PASS`
|
||||
|
||||
**Step 2: Run module compile**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn clean compile -pl ccdi-project -am
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `BUILD SUCCESS`
|
||||
|
||||
**Step 3: Record manual API smoke checklist**
|
||||
|
||||
手工检查以下接口行为:
|
||||
|
||||
- `DELETE /ccdi/file-upload/{id}` 删除 `parsed_success` 记录返回成功
|
||||
- 删除 `parsed_failed` 记录返回错误
|
||||
- 重复删除 `deleted` 记录返回错误
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "test: 完成上传文件删除后端回归验证"
|
||||
```
|
||||
Reference in New Issue
Block a user