Refactor project pages and update related docs

This commit is contained in:
wkc
2026-05-28 16:37:51 +08:00
parent 000e8698a5
commit 7ce721ef93
40 changed files with 730 additions and 785 deletions

View File

@@ -9,6 +9,7 @@ public final class CcdiProjectStatusConstants {
public static final String COMPLETED = "1";
public static final String ARCHIVED = "2";
public static final String TAGGING = "3";
public static final String TAG_FAILED = "4";
private CcdiProjectStatusConstants() {
}

View File

@@ -32,7 +32,7 @@ public class CcdiProject implements Serializable {
/** 配置方式default-全局默认custom-自定义 */
private String configType;
/** 项目状态0-进行中1-已完成2-已归档3-打标中 */
/** 项目状态0-进行中1-已完成2-已归档3-打标中4-打标失败 */
private String status;
/** 是否归档0-未归档1-已归档 */

View File

@@ -23,4 +23,7 @@ public class CcdiProjectStatusCountsVO {
/** 打标中项目数(状态3) */
private Long status3;
/** 打标失败项目数(状态4) */
private Long status4;
}

View File

@@ -50,6 +50,12 @@ public class CcdiProjectVO {
/** 更新时间 */
private Date updateTime;
/** 最近一次打标失败原因 */
private String latestTagTaskErrorMessage;
/** 最近一次打标失败结束时间 */
private Date latestTagTaskEndTime;
/** 创建者(用户名) */
private String createBy;

View File

@@ -32,4 +32,12 @@ public interface CcdiBankTagTaskMapper extends BaseMapper<CcdiBankTagTask> {
* @return 任务实体
*/
CcdiBankTagTask selectRunningTaskByProjectId(@Param("projectId") Long projectId);
/**
* 查询项目最近一次失败任务
*
* @param projectId 项目ID
* @return 任务实体
*/
CcdiBankTagTask selectLatestFailedTaskByProjectId(@Param("projectId") Long projectId);
}

View File

@@ -153,7 +153,7 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
task.setUpdateBy(operator);
task.setUpdateTime(new Date());
updateFailedTaskSafely(task, ex);
projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.PROCESSING, operator);
projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.TAG_FAILED, operator);
log.error("【流水标签】任务执行失败: taskId={}, projectId={}, modelCode={}, triggerType={}, error={}",
task.getId(), projectId, modelCode, triggerType, ex.getMessage(), ex);
throw ex;

View File

@@ -7,10 +7,12 @@ import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask;
import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import com.ruoyi.common.exception.ServiceException;
@@ -43,6 +45,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Resource
private CcdiProjectMapper projectMapper;
@Resource
private CcdiBankTagTaskMapper bankTagTaskMapper;
@Resource
private LsfxAnalysisClient lsfxAnalysisClient;
@@ -77,6 +82,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
// 5. 返回VO
CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(project, vo);
fillLatestTagFailure(project, vo);
return vo;
}
@@ -116,6 +122,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
}
CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(project, vo);
fillLatestTagFailure(project, vo);
return vo;
}
@@ -183,6 +190,12 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
);
vo.setStatus3(status3Count);
Long status4Count = projectMapper.selectCount(
new LambdaQueryWrapper<CcdiProject>()
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAG_FAILED)
);
vo.setStatus4(status4Count);
return vo;
}
@@ -263,10 +276,23 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
case CcdiProjectStatusConstants.COMPLETED -> "已完成";
case CcdiProjectStatusConstants.ARCHIVED -> "已归档";
case CcdiProjectStatusConstants.TAGGING -> "打标中";
case CcdiProjectStatusConstants.TAG_FAILED -> "打标失败";
default -> "未知";
};
}
private void fillLatestTagFailure(CcdiProject project, CcdiProjectVO vo) {
if (!CcdiProjectStatusConstants.TAG_FAILED.equals(project.getStatus())) {
return;
}
CcdiBankTagTask latestFailedTask = bankTagTaskMapper.selectLatestFailedTaskByProjectId(project.getProjectId());
if (latestFailedTask == null) {
return;
}
vo.setLatestTagTaskErrorMessage(latestFailedTask.getErrorMessage());
vo.setLatestTagTaskEndTime(latestFailedTask.getEndTime());
}
private String resolveOperator(String operator) {
return StringUtils.hasText(operator) ? operator : "system";
}

View File

@@ -65,4 +65,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
limit 1
</select>
<select id="selectLatestFailedTaskByProjectId" resultMap="CcdiBankTagTaskResultMap">
select id, project_id, trigger_type, model_code, status, need_rerun, success_rule_count,
failed_rule_count, hit_count, error_message, start_time, end_time,
create_by, create_time, update_by, update_time
from ccdi_bank_tag_task
where project_id = #{projectId}
and status = 'FAILED'
order by id desc
limit 1
</select>
</mapper>

View File

@@ -317,7 +317,7 @@ class CcdiBankTagServiceImplTest {
}
@Test
void shouldRollbackProjectStatusToProcessingWhenRebuildFails() {
void shouldMarkProjectTagFailedWhenRebuildFails() {
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
CcdiBankTagRule rule = buildRule("LARGE_TRANSACTION", "大额交易",
@@ -329,7 +329,7 @@ class CcdiBankTagServiceImplTest {
assertThrows(RuntimeException.class,
() -> service.rebuildProject(40L, null, "tester", TriggerType.MANUAL));
verify(projectService).updateProjectStatus(40L, "0", "tester");
verify(projectService).updateProjectStatus(40L, "4", "tester");
}
@Test

View File

@@ -112,7 +112,7 @@ class CcdiBankTagServiceRiskCountRefreshTest {
verify(taskMapper).updateTask(argThat(task -> "FAILED".equals(task.getStatus())
&& "refresh failed".equals(task.getErrorMessage())));
verify(projectService).updateProjectStatus(40L, "0", "tester");
verify(projectService).updateProjectStatus(40L, "4", "tester");
}
private CcdiBankTagRule buildRule() {

View File

@@ -7,10 +7,12 @@ import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask;
import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
@@ -25,11 +27,14 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.Date;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -47,6 +52,9 @@ class CcdiProjectServiceImplTest {
@Mock
private CcdiProjectMapper projectMapper;
@Mock
private CcdiBankTagTaskMapper bankTagTaskMapper;
@Mock
private LsfxAnalysisClient lsfxAnalysisClient;
@@ -55,13 +63,55 @@ class CcdiProjectServiceImplTest {
@Test
void shouldCountTaggingProjectsSeparately() {
when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L);
when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L, 0L);
CcdiProjectStatusCountsVO counts = service.getStatusCounts();
assertEquals(1L, counts.getStatus3());
}
@Test
void shouldCountTagFailedProjectsSeparately() {
when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L, 5L);
CcdiProjectStatusCountsVO counts = service.getStatusCounts();
assertEquals(5L, counts.getStatus4());
}
@Test
void shouldReturnLatestFailedTagTaskOnFailedProjectDetail() {
Date endTime = new Date();
CcdiProject project = new CcdiProject();
project.setProjectId(40L);
project.setStatus("4");
when(projectMapper.selectById(40L)).thenReturn(project);
CcdiBankTagTask failedTask = new CcdiBankTagTask();
failedTask.setErrorMessage("threshold missing");
failedTask.setEndTime(endTime);
when(bankTagTaskMapper.selectLatestFailedTaskByProjectId(40L)).thenReturn(failedTask);
CcdiProjectVO result = service.getProjectById(40L);
assertEquals("threshold missing", result.getLatestTagTaskErrorMessage());
assertEquals(endTime, result.getLatestTagTaskEndTime());
}
@Test
void shouldNotReturnLatestFailedTagTaskWhenProjectIsNotFailed() {
CcdiProject project = new CcdiProject();
project.setProjectId(40L);
project.setStatus("0");
when(projectMapper.selectById(40L)).thenReturn(project);
CcdiProjectVO result = service.getProjectById(40L);
assertNotNull(result);
assertNull(result.getLatestTagTaskErrorMessage());
verify(bankTagTaskMapper, never()).selectLatestFailedTaskByProjectId(any());
}
@Test
void shouldRejectUpdatingArchivedProjectToTagging() {
CcdiProject archived = new CcdiProject();
@@ -84,6 +134,16 @@ class CcdiProjectServiceImplTest {
() -> service.ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数"));
}
@Test
void shouldAllowWritingWhenProjectTagFailed() {
CcdiProject tagFailed = new CcdiProject();
tagFailed.setProjectId(40L);
tagFailed.setStatus("4");
when(projectMapper.selectById(40L)).thenReturn(tagFailed);
assertDoesNotThrow(() -> service.ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数"));
}
@Test
void shouldArchiveCompletedProject() {
CcdiProject project = new CcdiProject();
@@ -110,6 +170,16 @@ class CcdiProjectServiceImplTest {
assertThrows(ServiceException.class, () -> service.archiveProject(41L, "tester"));
}
@Test
void shouldRejectArchivingProjectWhenStatusIsTagFailed() {
CcdiProject project = new CcdiProject();
project.setProjectId(41L);
project.setStatus("4");
when(projectMapper.selectById(41L)).thenReturn(project);
assertThrows(ServiceException.class, () -> service.archiveProject(41L, "tester"));
}
@Test
void shouldRejectWritingWhenProjectIsArchived() {
CcdiProject archived = new CcdiProject();

View File

@@ -14,12 +14,25 @@ class CcdiProjectStatusSqlTest {
void shouldContainTaggingStatusInInitAndMigrationSql() throws IOException {
Path repoRoot = Path.of("..");
String initSql = Files.readString(repoRoot.resolve("sql/ccdi_project.sql"));
String prodInitSql = Files.readString(repoRoot.resolve("sql/ccdi_prod_init.sql"));
String migrationSql = Files.readString(repoRoot.resolve("sql/migration/2026-03-18-add-project-tagging-status.sql"));
String tagFailedMigrationSql =
Files.readString(repoRoot.resolve("sql/migration/2026-05-27-add-project-tag-failed-status.sql"));
assertTrue(initSql.contains("打标中"));
assertTrue(initSql.contains("'3'"));
assertTrue(migrationSql.contains("ccdi_project_status"));
assertTrue(migrationSql.contains("打标中"));
assertTrue(migrationSql.contains("'3'"));
assertTrue(initSql.contains("打标失败"));
assertTrue(initSql.contains("'4'"));
assertTrue(prodInitSql.contains("打标失败"));
assertTrue(prodInitSql.contains("'4','ccdi_project_status'"));
assertTrue(tagFailedMigrationSql.contains("ccdi_project_status"));
assertTrue(tagFailedMigrationSql.contains("打标失败"));
assertTrue(tagFailedMigrationSql.contains("'4'"));
assertTrue(tagFailedMigrationSql.contains("latest_task.status = 'FAILED'"));
assertTrue(tagFailedMigrationSql.contains("project.status IN ('0', '3')"));
}
}