# Project Bank Statement Tagging Logging Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 为项目流水标签后端补齐可排障、可审计的详细日志，覆盖手动重算、自动触发、项目互斥、规则执行、参数解析和补跑收尾链路。

**Architecture:** 继续沿用现有 `ccdi-project` 结构，在 Controller、上传服务、重算协调器、标签服务和参数解析器上直接补充统一格式日志，不引入新框架或新表。测试沿用现有 JUnit 5 + Mockito 基础，在现有测试类中通过 Logback `ListAppender` 捕获日志，验证关键分支会产生日志摘要且不破坏原行为。

**Tech Stack:** Java 21, Spring Boot 3, Lombok, SLF4J + Logback, JUnit 5, Mockito, Maven

---

## File Structure

- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java`
  手动重算入口日志，记录 `projectId/modelCode/operator`。
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
  自动触发入口日志，记录批处理完成后的触发、跳过和提交结果。
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java`
  项目级锁、互斥拒绝、`needRerun` 标记与补跑消费日志。
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`
  任务生命周期、规则执行、结果清理和批量写入摘要日志。
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java`
  规则参数来源、解析结果、缺失参数日志。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java`
  手动重算入口日志测试。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
  自动触发入口日志测试。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java`
  协调器互斥和补跑日志测试。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`
  任务成功、无命中、失败摘要日志测试。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java`
  参数解析成功和缺失日志测试。

### Task 1: 补齐入口层日志

**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.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/controller/CcdiBankTagControllerTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`

- [ ] **Step 1: Write the failing tests**

在控制器测试中新增手动重算入口日志断言，在上传服务测试中新增自动触发“跳过”和“提交”日志断言。优先复用现有 Logback `ListAppender` 方案。

```java
@Test
void rebuild_shouldLogManualRebuildRequest() {
    CcdiBankTagRebuildDTO dto = new CcdiBankTagRebuildDTO();
    dto.setProjectId(40L);
    dto.setModelCode("LARGE_TRANSACTION");

    Logger logger = (Logger) LoggerFactory.getLogger(CcdiBankTagController.class);
    ListAppender<ILoggingEvent> appender = new ListAppender<>();
    appender.start();
    logger.addAppender(appender);

    when(bankTagService.submitRebuild(dto, "admin")).thenReturn("标签重算任务已提交");

    try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
        mocked.when(SecurityUtils::getUsername).thenReturn("admin");
        controller.rebuild(dto);
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("收到手动重算请求")
                && message.contains("projectId=40")
                && message.contains("modelCode=LARGE_TRANSACTION")
                && message.contains("operator=admin")));
    } finally {
        logger.detachAppender(appender);
    }
}

@Test
void handleTagRebuildAfterBatchCompletion_shouldLogSkipWhenAllRecordsFailed() {
    Logger logger = (Logger) LoggerFactory.getLogger(CcdiFileUploadServiceImpl.class);
    ListAppender<ILoggingEvent> appender = new ListAppender<>();
    appender.start();
    logger.addAppender(appender);

    try {
        ReflectionTestUtils.invokeMethod(
            service,
            "handleTagRebuildAfterBatchCompletion",
            PROJECT_ID,
            TriggerType.AUTO_BATCH_UPLOAD,
            Boolean.FALSE
        );

        verify(bankTagService, never()).submitAutoRebuild(any(), any());
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("跳过自动重算")
                && message.contains("projectId=100")
                && message.contains("AUTO_BATCH_UPLOAD")));
    } finally {
        logger.detachAppender(appender);
    }
}
```

- [ ] **Step 2: Run tests to verify they fail**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest#rebuild_shouldLogManualRebuildRequest,CcdiFileUploadServiceImplTest#handleTagRebuildAfterBatchCompletion_shouldLogSkipWhenAllRecordsFailed
```

Expected:

- `FAIL`
- 原因是控制器和上传服务尚未输出对应日志

- [ ] **Step 3: Write minimal implementation**

在入口层补日志，保持统一字段格式：

```java
log.info("【流水标签】收到手动重算请求: projectId={}, modelCode={}, operator={}",
    dto.getProjectId(), dto.getModelCode(), operator);
```

```java
log.info("【流水标签】批处理完成，准备触发自动重算: projectId={}, triggerType={}, anySuccess={}",
    projectId, triggerType, anySuccess);
if (!Boolean.TRUE.equals(anySuccess)) {
    log.warn("【流水标签】跳过自动重算: projectId={}, triggerType={}, reason=all_records_failed",
        projectId, triggerType);
    return;
}
bankTagService.submitAutoRebuild(projectId, triggerType);
```

- [ ] **Step 4: Run tests to verify they pass**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest#rebuild_shouldLogManualRebuildRequest,CcdiFileUploadServiceImplTest#handleTagRebuildAfterBatchCompletion_shouldLogSkipWhenAllRecordsFailed
```

Expected:

- `PASS`

- [ ] **Step 5: Commit**

```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
git commit -m "test: 补充流水标签入口日志"
```

### Task 2: 补齐协调器互斥与补跑日志

**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java`

- [ ] **Step 1: Write the failing tests**

在现有协调器测试基础上增加日志捕获，覆盖“手动被拒绝”和“自动标记补跑”两个分支。

```java
@Test
void submitManual_shouldLogRejectWhenProjectAlreadyRunning() {
    CcdiBankTagTask runningTask = new CcdiBankTagTask();
    runningTask.setId(1L);
    runningTask.setProjectId(40L);
    runningTask.setStatus("RUNNING");
    when(taskMapper.selectRunningTaskByProjectId(40L)).thenReturn(runningTask);

    Logger logger = (Logger) LoggerFactory.getLogger(ProjectBankTagRebuildCoordinator.class);
    ListAppender<ILoggingEvent> appender = new ListAppender<>();
    appender.start();
    logger.addAppender(appender);

    try {
        assertThrows(ServiceException.class, () -> coordinator.submitManual(40L, null, "admin"));
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("拒绝手动重算")
                && message.contains("projectId=40")
                && message.contains("operator=admin")));
    } finally {
        logger.detachAppender(appender);
    }
}

@Test
void submitAuto_shouldLogNeedRerunWhenProjectAlreadyRunning() {
    CcdiBankTagTask runningTask = new CcdiBankTagTask();
    runningTask.setId(1L);
    runningTask.setProjectId(40L);
    runningTask.setStatus("RUNNING");
    runningTask.setNeedRerun(0);
    when(taskMapper.selectRunningTaskByProjectId(40L)).thenReturn(runningTask);

    Logger logger = (Logger) LoggerFactory.getLogger(ProjectBankTagRebuildCoordinator.class);
    ListAppender<ILoggingEvent> appender = new ListAppender<>();
    appender.start();
    logger.addAppender(appender);

    try {
        coordinator.submitAuto(40L, TriggerType.AUTO_BATCH_UPLOAD);
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("已标记完成后补跑")
                && message.contains("runningTaskId=1")
                && message.contains("AUTO_BATCH_UPLOAD")));
    } finally {
        logger.detachAppender(appender);
    }
}
```

- [ ] **Step 2: Run tests to verify they fail**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=ProjectBankTagRebuildCoordinatorTest#submitManual_shouldLogRejectWhenProjectAlreadyRunning+submitAuto_shouldLogNeedRerunWhenProjectAlreadyRunning
```

Expected:

- `FAIL`
- 原因是协调器尚未输出拒绝和补跑日志

- [ ] **Step 3: Write minimal implementation**

在协调器中补 `info/warn` 日志，至少覆盖：

```java
log.info("【流水标签】手动重算开始排队: projectId={}, modelCode={}, operator={}", projectId, modelCode, operator);
log.warn("【流水标签】项目已有运行中任务，拒绝手动重算: projectId={}, modelCode={}, operator={}", projectId, modelCode, operator);
log.warn("【流水标签】项目正在重算，已标记完成后补跑: projectId={}, runningTaskId={}, triggerType={}", projectId, runningTask.getId(), triggerType);
log.info("【流水标签】获取项目重算锁成功: projectId={}", projectId);
log.info("【流水标签】释放项目重算锁: projectId={}", projectId);
```

如实现过程中发现 `taskId` 为空分支，日志允许保留空值，但不要省略字段。

- [ ] **Step 4: Run tests to verify they pass**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=ProjectBankTagRebuildCoordinatorTest#submitManual_shouldLogRejectWhenProjectAlreadyRunning+submitAuto_shouldLogNeedRerunWhenProjectAlreadyRunning
```

Expected:

- `PASS`

- [ ] **Step 5: Commit**

```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java
git commit -m "test: 补充流水标签协调器日志"
```

### Task 3: 补齐任务级与规则级执行日志

**Files:**
- 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/CcdiBankTagServiceImplTest.java`

- [ ] **Step 1: Write the failing tests**

新增两个测试，分别覆盖成功摘要和失败摘要。复用同步执行器 `Runnable::run`，避免并发影响日志顺序。

```java
@Test
void rebuildProject_shouldLogTaskLifecycleAndRuleSummary() {
    ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);

    CcdiBankTagRule rule = new CcdiBankTagRule();
    rule.setModelCode("LARGE_TRANSACTION");
    rule.setModelName("大额交易");
    rule.setRuleCode("HOUSE_OR_CAR_EXPENSE");
    rule.setRuleName("房车消费支出交易");
    rule.setResultType("STATEMENT");

    BankTagRuleExecutionConfig config = new BankTagRuleExecutionConfig();
    config.setProjectId(40L);
    config.setRuleMeta(rule);

    BankTagStatementHitVO hit = new BankTagStatementHitVO();
    hit.setBankStatementId(10L);
    hit.setGroupId(40);
    hit.setLogId(40001);
    hit.setReasonDetail("命中房车消费支出");

    doAnswer(invocation -> {
        CcdiBankTagTask task = invocation.getArgument(0);
        task.setId(88L);
        return 1;
    }).when(taskMapper).insertTask(any(CcdiBankTagTask.class));

    when(ruleMapper.selectEnabledRules(null)).thenReturn(List.of(rule));
    when(configResolver.resolve(40L, rule)).thenReturn(config);
    when(analysisMapper.selectHouseOrCarExpenseStatements(40L)).thenReturn(List.of(hit));

    Logger logger = (Logger) LoggerFactory.getLogger(CcdiBankTagServiceImpl.class);
    ListAppender<ILoggingEvent> appender = new ListAppender<>();
    appender.start();
    logger.addAppender(appender);

    try {
        service.rebuildProject(40L, null, "admin", TriggerType.MANUAL);
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("任务创建成功")
                && message.contains("taskId=88")
                && message.contains("projectId=40")));
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("规则执行完成")
                && message.contains("ruleCode=HOUSE_OR_CAR_EXPENSE")
                && message.contains("hitCount=1")));
    } finally {
        logger.detachAppender(appender);
    }
}

@Test
void rebuildProject_shouldLogFailureSummaryWhenRuleExecutionFails() {
    ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);

    CcdiBankTagRule rule = new CcdiBankTagRule();
    rule.setModelCode("LARGE_TRANSACTION");
    rule.setRuleCode("HOUSE_OR_CAR_EXPENSE");
    rule.setResultType("STATEMENT");

    doAnswer(invocation -> {
        CcdiBankTagTask task = invocation.getArgument(0);
        task.setId(89L);
        return 1;
    }).when(taskMapper).insertTask(any(CcdiBankTagTask.class));

    when(ruleMapper.selectEnabledRules(null)).thenReturn(List.of(rule));
    when(configResolver.resolve(40L, rule)).thenThrow(new RuntimeException("threshold missing"));

    Logger logger = (Logger) LoggerFactory.getLogger(CcdiBankTagServiceImpl.class);
    ListAppender<ILoggingEvent> appender = new ListAppender<>();
    appender.start();
    logger.addAppender(appender);

    try {
        assertThrows(RuntimeException.class, () -> service.rebuildProject(40L, null, "admin", TriggerType.MANUAL));
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("任务执行失败")
                && message.contains("taskId=89")
                && message.contains("threshold missing")));
    } finally {
        logger.detachAppender(appender);
    }
}
```

- [ ] **Step 2: Run tests to verify they fail**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagServiceImplTest#rebuildProject_shouldLogTaskLifecycleAndRuleSummary,CcdiBankTagServiceImplTest#rebuildProject_shouldLogFailureSummaryWhenRuleExecutionFails
```

Expected:

- `FAIL`
- 原因是标签服务尚未输出任务和规则摘要日志

- [ ] **Step 3: Write minimal implementation**

在标签服务中补统一摘要日志，并尽量把时间统计放在方法内部完成：

```java
log.info("【流水标签】任务创建成功: taskId={}, projectId={}, modelCode={}, triggerType={}, operator={}",
    task.getId(), projectId, modelCode, triggerType, operator);
log.info("【流水标签】加载启用规则完成: taskId={}, projectId={}, modelCode={}, ruleCount={}",
    task.getId(), projectId, modelCode, rules.size());
log.info("【流水标签】开始清理历史结果: taskId={}, projectId={}, modelCode={}",
    task.getId(), projectId, modelCode);
log.info("【流水标签】规则开始执行: taskId={}, projectId={}, ruleCode={}, resultType={}",
    taskId, projectId, rule.getRuleCode(), rule.getResultType());
log.debug("【流水标签】规则执行参数: taskId={}, ruleCode={}, thresholds={}", taskId, rule.getRuleCode(), config.getThresholdValues());
log.info("【流水标签】规则执行完成: taskId={}, projectId={}, ruleCode={}, hitCount={}, costMs={}",
    taskId, projectId, rule.getRuleCode(), results.size(), costMs);
log.error("【流水标签】任务执行失败: taskId={}, projectId={}, modelCode={}, triggerType={}, error={}",
    task.getId(), projectId, modelCode, triggerType, ex.getMessage(), ex);
```

如果实现过程中发现 `executeRule(...)` 缺少 `taskId` 上下文，可按最小改动为私有方法补一个 `taskId` 参数，不要引入全局上下文对象。

- [ ] **Step 4: Run tests to verify they pass**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagServiceImplTest#rebuildProject_shouldLogTaskLifecycleAndRuleSummary,CcdiBankTagServiceImplTest#rebuildProject_shouldLogFailureSummaryWhenRuleExecutionFails
```

Expected:

- `PASS`

- [ ] **Step 5: Commit**

```bash
git add 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 "test: 补充流水标签执行日志"
```

### Task 4: 补齐规则参数解析日志

**Files:**
- Modify: `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 tests**

新增一个日志测试，验证解析成功时会记录参数来源，参数缺失时会记录缺失编码。

```java
@Test
void resolve_shouldLogThresholdSourceAndMissingParams() {
    CcdiProject project = new CcdiProject();
    project.setProjectId(40L);
    project.setConfigType("default");
    when(projectMapper.selectById(40L)).thenReturn(project);
    when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
        buildParam("LARGE_CASH_DEPOSIT", "50000")
    ));

    CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
    ruleMeta.setModelCode("LARGE_TRANSACTION");
    ruleMeta.setRuleCode("FREQUENT_CASH_DEPOSIT");

    Logger logger = (Logger) LoggerFactory.getLogger(BankTagRuleConfigResolver.class);
    ListAppender<ILoggingEvent> appender = new ListAppender<>();
    appender.start();
    logger.addAppender(appender);

    try {
        resolver.resolve(40L, ruleMeta);
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("解析规则参数")
                && message.contains("effectiveProjectId=0")
                && message.contains("FREQUENT_CASH_DEPOSIT")));
        assertTrue(appender.list.stream().map(ILoggingEvent::getFormattedMessage)
            .anyMatch(message -> message.contains("规则参数缺失")
                && message.contains("FREQUENT_CASH_DEPOSIT")));
    } finally {
        logger.detachAppender(appender);
    }
}
```

- [ ] **Step 2: Run test to verify it fails**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=BankTagRuleConfigResolverTest#resolve_shouldLogThresholdSourceAndMissingParams
```

Expected:

- `FAIL`
- 原因是参数解析器尚未输出来源和缺失日志

- [ ] **Step 3: Write minimal implementation**

在参数解析器中补日志，并显式计算缺失参数集合：

```java
log.info("【流水标签】解析规则参数: projectId={}, effectiveProjectId={}, ruleCode={}, requiredParams={}",
    projectId, effectiveProjectId, ruleMeta.getRuleCode(), requiredParamCodes);
log.debug("【流水标签】规则参数解析结果: projectId={}, ruleCode={}, thresholdValues={}",
    projectId, ruleMeta.getRuleCode(), thresholdValues);
if (!missingParamCodes.isEmpty()) {
    log.warn("【流水标签】规则参数缺失: projectId={}, ruleCode={}, missingParams={}",
        projectId, ruleMeta.getRuleCode(), missingParamCodes);
}
```

不要在这里改变现有返回语义；本任务只补日志，不新增抛错逻辑。

- [ ] **Step 4: Run test to verify it passes**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=BankTagRuleConfigResolverTest#resolve_shouldLogThresholdSourceAndMissingParams
```

Expected:

- `PASS`

- [ ] **Step 5: Commit**

```bash
git add 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 "test: 补充流水标签参数解析日志"
```

### Task 5: 跑回归并整理最终提交

**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
- Modify: `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`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java`

- [ ] **Step 1: Run focused regression tests**

Run:

```bash
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest,CcdiFileUploadServiceImplTest,ProjectBankTagRebuildCoordinatorTest,CcdiBankTagServiceImplTest,BankTagRuleConfigResolverTest
```

Expected:

- `PASS`
- 所有日志相关测试通过

- [ ] **Step 2: Run module compile to catch logging import or signature regressions**

Run:

```bash
mvn clean compile -pl ccdi-project -am -DskipTests
```

Expected:

- `BUILD SUCCESS`

- [ ] **Step 3: Review final diff**

Run:

```bash
git diff -- ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java 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/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java
```

Expected:

- 只包含本次日志相关改动
- 没有引入敏感字段明文打印

- [ ] **Step 4: Commit**

```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java 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/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java
git commit -m "feat: 补充流水标签详细日志"
```
