Files
ccdi/docs/plans/backend/2026-05-06-bank-upload-original-filename-backend-implementation.md

25 KiB
Raw Blame History

Bank Upload Original Filename Implementation Plan

For agentic workers: Steps use checkbox (- [ ]) syntax for tracking. Follow the CCDI project rule: unless the user explicitly declares using-superpowers for implementation, execute this plan through the ordinary workflow and do not enable subagents by default.

Goal: 上传本地流水文件后,页面记录名和转传流水分析平台 multipart 文件名都保持用户初始上传文件名。

Architecture: 后端继续使用唯一临时文件名保存文件,避免同名和并发冲突;上传流水分析平台时额外传入原始文件名,并用可覆盖 Resource#getFilename() 的资源对象构造 multipart 文件 part。上传记录状态后处理增加“是否保留当前记录文件名”的来源参数本地上传链路保留初始文件名拉取本行信息链路保持现状。

Tech Stack: Java 21, Spring Boot 3, RestTemplate, MyBatis Plus, JUnit 5, Mockito, Vue 2 页面真实验证。


Project Notes

  • 设计文档:docs/superpowers/specs/2026-05-06-bank-upload-original-filename-design.md
  • 项目路径规则覆盖 writing-plans 默认目录:本次只涉及后端源码,因此实施计划放在 docs/plans/backend/
  • 本次不新增前端源码,不新增数据库字段,不回改历史上传记录。
  • .DS_Store 忽略,不纳入暂存或提交。
  • 完成实现后必须新增实施记录:docs/reports/implementation/2026-05-06-bank-upload-original-filename-implementation.md
  • 真实页面验证使用 @browser-use:browser 打开实际业务页面,不打开 prototype 页面。
  • 如果启动了后端、前端或 lsfx-mock-server,测试结束后必须关闭本轮启动的进程。

File Map

  • Modify: ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java
    • 新增可指定 multipart filename 的 NamedFileSystemResource
    • uploadFile 继续支持 File 参数;当参数已经是 Resource 时直接加入 multipart body。
  • Modify: ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/LsfxAnalysisClient.java
    • 新增 uploadFile(Integer groupId, File file, String uploadFileName)
    • 现有 uploadFile(Integer groupId, File file) 委托到新方法并使用 file.getName()
    • 项目上传链路使用新方法传入初始上传文件名。
  • Create: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/util/HttpUtilTest.java
    • 验证 multipart body 中 files 资源的 filename 可被显式指定。
  • Create: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/LsfxAnalysisClientTest.java
    • 验证流水分析客户端上传时把指定原始文件名写入 files 参数。
  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java
    • processFileAsync 调用 lsfxClient.uploadFile(lsfxProjectId, file, record.getFileName())
    • processRecordAfterLogIdReady 增加来源控制参数,本地上传不覆盖 record.fileName,拉取本行信息保持现有覆盖行为。
  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
    • 更新现有 processFileAsync 上传 stubbing 到三参数签名。
    • 新增本地上传使用原始文件名转传、平台返回名不覆盖上传记录、拉取本行信息保持现状的回归测试。
  • Create: docs/reports/implementation/2026-05-06-bank-upload-original-filename-implementation.md
    • 记录修改内容、影响范围、测试命令、真实页面验证和进程清理情况。

Task 1: Add Failing Tests For Multipart Filename

Files:

  • Create: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/util/HttpUtilTest.java

  • Create: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/LsfxAnalysisClientTest.java

  • Step 1: Create HttpUtilTest with a failing filename assertion

Add:

package com.ruoyi.lsfx.util;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class HttpUtilTest {

    @Mock
    private RestTemplate restTemplate;

    @TempDir
    Path tempDir;

    @Test
    void uploadFile_shouldUseExplicitResourceFilename() throws Exception {
        HttpUtil httpUtil = new HttpUtil();
        ReflectionTestUtils.setField(httpUtil, "restTemplate", restTemplate);

        Path tempFile = tempDir.resolve("batch_0_123456.xlsx");
        Files.writeString(tempFile, "content");

        ArgumentCaptor<HttpEntity> captor = ArgumentCaptor.forClass(HttpEntity.class);
        when(restTemplate.postForEntity(eq("http://lsfx/upload"), captor.capture(), eq(String.class)))
            .thenReturn(ResponseEntity.ok("ok"));

        Map<String, Object> params = new HashMap<>();
        params.put("groupId", 200);
        params.put("files", HttpUtil.namedFileResource(tempFile.toFile(), "银行流水A.xlsx"));

        String result = httpUtil.uploadFile("http://lsfx/upload", params, null, String.class);

        assertEquals("ok", result);
        MultiValueMap<String, Object> body = (MultiValueMap<String, Object>) captor.getValue().getBody();
        Object filePart = body.getFirst("files");
        Resource resource = assertInstanceOf(Resource.class, filePart);
        assertEquals("银行流水A.xlsx", resource.getFilename());
    }
}
  • Step 2: Create LsfxAnalysisClientTest with a failing client assertion

Add:

package com.ruoyi.lsfx.client;

import com.ruoyi.lsfx.constants.LsfxConstants;
import com.ruoyi.lsfx.domain.response.UploadFileResponse;
import com.ruoyi.lsfx.util.HttpUtil;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.Resource;
import org.springframework.test.util.ReflectionTestUtils;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class LsfxAnalysisClientTest {

    @Mock
    private HttpUtil httpUtil;

    @InjectMocks
    private LsfxAnalysisClient client;

    @TempDir
    Path tempDir;

    @Test
    void uploadFile_shouldPassOriginalFilenameToMultipartResource() throws Exception {
        ReflectionTestUtils.setField(client, "baseUrl", "http://lsfx");
        ReflectionTestUtils.setField(client, "uploadFileEndpoint", "/upload");
        ReflectionTestUtils.setField(client, "clientId", "client-1");

        Path tempFile = tempDir.resolve("batch_0_123456.xlsx");
        Files.writeString(tempFile, "content");

        UploadFileResponse response = new UploadFileResponse();
        response.setData(new UploadFileResponse.UploadData());

        ArgumentCaptor<Map<String, Object>> paramsCaptor = ArgumentCaptor.forClass(Map.class);
        ArgumentCaptor<Map<String, String>> headersCaptor = ArgumentCaptor.forClass(Map.class);
        when(httpUtil.uploadFile(eq("http://lsfx/upload"), paramsCaptor.capture(), headersCaptor.capture(), eq(UploadFileResponse.class)))
            .thenReturn(response);

        client.uploadFile(200, tempFile.toFile(), "银行流水A.xlsx");

        assertEquals(200, paramsCaptor.getValue().get("groupId"));
        Resource filePart = assertInstanceOf(Resource.class, paramsCaptor.getValue().get("files"));
        assertEquals("银行流水A.xlsx", filePart.getFilename());
        assertEquals("client-1", headersCaptor.getValue().get(LsfxConstants.HEADER_CLIENT_ID));
    }
}
  • Step 3: Run tests and confirm they fail

Run:

mvn -pl ccdi-lsfx -am -Dtest=HttpUtilTest,LsfxAnalysisClientTest -Dsurefire.failIfNoSpecifiedTests=false test

Expected: FAIL because HttpUtil.namedFileResource(...) and LsfxAnalysisClient.uploadFile(Integer, File, String) do not exist yet.

Task 2: Implement Multipart Filename Support

Files:

  • Modify: ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java

  • Modify: ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/LsfxAnalysisClient.java

  • Step 1: Add named file resource to HttpUtil

In HttpUtil.java, add only this import:

import org.springframework.util.StringUtils;

Do not import org.springframework.core.io.Resource in this file because it conflicts with the existing jakarta.annotation.Resource annotation import. Use the Spring Resource type by fully qualified name in implementation snippets.

Add this nested class and factory method inside HttpUtil:

public static org.springframework.core.io.Resource namedFileResource(File file, String filename) {
    return new NamedFileSystemResource(file, filename);
}

private static class NamedFileSystemResource extends FileSystemResource {
    private final String filename;

    NamedFileSystemResource(File file, String filename) {
        super(file);
        this.filename = StringUtils.hasText(filename) ? filename : file.getName();
    }

    @Override
    public String getFilename() {
        return filename;
    }
}
  • Step 2: Let HttpUtil.uploadFile accept existing Resource values

Replace the file branch in uploadFile(...) with:

if (value instanceof File) {
    File file = (File) value;
    body.add(key, new FileSystemResource(file));
} else if (value instanceof org.springframework.core.io.Resource) {
    body.add(key, value);
} else {
    body.add(key, value);
}
  • Step 3: Add explicit filename upload overload to LsfxAnalysisClient

Replace the current upload method body with a delegating overload:

public UploadFileResponse uploadFile(Integer groupId, File file) {
    return uploadFile(groupId, file, file.getName());
}

public UploadFileResponse uploadFile(Integer groupId, File file, String uploadFileName) {
    String multipartFileName = org.springframework.util.StringUtils.hasText(uploadFileName)
        ? uploadFileName
        : file.getName();
    log.info("【流水分析】上传文件请求: groupId={}, fileName={}", groupId, multipartFileName);
    long startTime = System.currentTimeMillis();

    try {
        String url = baseUrl + uploadFileEndpoint;

        Map<String, Object> params = new HashMap<>();
        params.put("groupId", groupId);
        params.put("files", HttpUtil.namedFileResource(file, multipartFileName));

        Map<String, String> headers = new HashMap<>();
        headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);

        UploadFileResponse response = httpUtil.uploadFile(url, params, headers, UploadFileResponse.class);

        long elapsed = System.currentTimeMillis() - startTime;
        if (response != null && response.getData() != null) {
            log.info("【流水分析】上传文件成功: uploadStatus={}, 耗时={}ms",
                response.getData().getUploadStatus(), elapsed);
        } else {
            log.warn("【流水分析】上传文件响应异常: 耗时={}ms", elapsed);
        }

        return response;
    } catch (LsfxApiException e) {
        log.error("【流水分析】上传文件失败: groupId={}, error={}", groupId, e.getMessage(), e);
        throw e;
    } catch (Exception e) {
        log.error("【流水分析】上传文件未知异常: groupId={}", groupId, e);
        throw new LsfxApiException("上传文件失败: " + e.getMessage(), e);
    }
}
  • Step 4: Run ccdi-lsfx tests

Run:

mvn -pl ccdi-lsfx -am -Dtest=HttpUtilTest,LsfxAnalysisClientTest,CreditParseControllerTest -Dsurefire.failIfNoSpecifiedTests=false test

Expected: PASS.

  • Step 5: Commit lsfx client changes

Check the worktree, stage only task files, then verify staged scope:

git status --short
git add ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java \
  ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/LsfxAnalysisClient.java \
  ccdi-lsfx/src/test/java/com/ruoyi/lsfx/util/HttpUtilTest.java \
  ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/LsfxAnalysisClientTest.java
git diff --cached --name-status

Expected staged files: only the four ccdi-lsfx files in this task.

git commit -m "修复流水分析上传文件名传递"

Task 3: Add Failing Project Upload Service Tests

Files:

  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java

  • Step 1: Update existing processFileAsync stubs to the new signature

In CcdiFileUploadServiceImplTest.java, replace local file upload stubs:

when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any())).thenReturn(buildUploadResponse());

with:

when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any(), org.mockito.ArgumentMatchers.anyString()))
    .thenReturn(buildUploadResponse());

Only update tests that exercise processFileAsync. Do not change processPullBankInfoAsync tests to upload files; that chain uses fetchInnerFlow.

  • Step 2: Add test that local upload calls LSFX with original record filename

Add:

@Test
void processFileAsync_shouldUploadToLsfxWithOriginalRecordFileName() throws IOException {
    when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any(), eq("原始流水.xlsx")))
        .thenReturn(buildUploadResponse());
    when(lsfxClient.checkParseStatus(LSFX_PROJECT_ID, String.valueOf(LOG_ID)))
        .thenReturn(buildCheckParseStatusResponse(false));
    when(lsfxClient.getFileUploadStatus(any())).thenReturn(buildParsedSuccessStatusResponse());
    when(lsfxClient.getBankStatement(any(GetBankStatementRequest.class)))
        .thenReturn(buildEmptyBankStatementResponse());

    CcdiFileUploadRecord record = buildRecord();
    record.setFileName("原始流水.xlsx");
    Path tempFile = createTempFile();

    service.processFileAsync(PROJECT_ID, LSFX_PROJECT_ID, tempFile.toString(), RECORD_ID, "batch-1", record);

    verify(lsfxClient).uploadFile(eq(LSFX_PROJECT_ID), argThat(file ->
        file.getName().startsWith("upload-") && file.getName().endsWith(".xlsx")
    ), eq("原始流水.xlsx"));
}
  • Step 3: Add test that platform filename does not overwrite local upload record filename

Add:

@Test
void processFileAsync_shouldKeepOriginalFileNameWhenStatusReturnsDifferentName() throws IOException {
    when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any(), org.mockito.ArgumentMatchers.anyString()))
        .thenReturn(buildUploadResponse());
    when(lsfxClient.checkParseStatus(LSFX_PROJECT_ID, String.valueOf(LOG_ID)))
        .thenReturn(buildCheckParseStatusResponse(false));
    when(lsfxClient.getFileUploadStatus(any()))
        .thenReturn(buildParsedSuccessStatusResponse("平台返回文件名.xlsx"));
    when(lsfxClient.getBankStatement(any(GetBankStatementRequest.class)))
        .thenReturn(buildEmptyBankStatementResponse());

    CcdiFileUploadRecord record = buildRecord();
    record.setFileName("原始流水.xlsx");
    Path tempFile = createTempFile();

    service.processFileAsync(PROJECT_ID, LSFX_PROJECT_ID, tempFile.toString(), RECORD_ID, "batch-1", record);

    verify(recordMapper, org.mockito.Mockito.atLeastOnce()).updateById(
        org.mockito.ArgumentMatchers.<CcdiFileUploadRecord>argThat(item ->
            "parsed_success".equals(item.getFileStatus())
                && "原始流水.xlsx".equals(item.getFileName()))
    );
}
  • Step 4: Keep pull-bank-info regression explicit

Keep the existing test processPullBankInfoAsync_shouldUpdateFileSizeFromStatusResponse asserting:

"XX身份证.xlsx".equals(item.getFileName())

This verifies the shared status method still allows platform filename overwrite for the “拉取本行信息” chain.

  • Step 5: Add failure-state filename regression

Add:

@Test
void processFileAsync_shouldKeepOriginalFileNameWhenParseStatusFails() throws IOException {
    GetFileUploadStatusResponse statusResponse = buildParsedSuccessStatusResponse("平台失败文件名.xlsx");
    GetFileUploadStatusResponse.LogItem logItem = statusResponse.getData().getLogs().get(0);
    logItem.setStatus(-1);
    logItem.setUploadStatusDesc("parse.failed");

    when(lsfxClient.uploadFile(eq(LSFX_PROJECT_ID), any(), org.mockito.ArgumentMatchers.anyString()))
        .thenReturn(buildUploadResponse());
    when(lsfxClient.checkParseStatus(LSFX_PROJECT_ID, String.valueOf(LOG_ID)))
        .thenReturn(buildCheckParseStatusResponse(false));
    when(lsfxClient.getFileUploadStatus(any())).thenReturn(statusResponse);

    CcdiFileUploadRecord record = buildRecord();
    record.setFileName("原始流水.xlsx");
    Path tempFile = createTempFile();

    service.processFileAsync(PROJECT_ID, LSFX_PROJECT_ID, tempFile.toString(), RECORD_ID, "batch-1", record);

    verify(recordMapper, org.mockito.Mockito.atLeastOnce()).updateById(
        org.mockito.ArgumentMatchers.<CcdiFileUploadRecord>argThat(item ->
            "parsed_failed".equals(item.getFileStatus())
                && "原始流水.xlsx".equals(item.getFileName()))
    );
}
  • Step 6: Run service tests and confirm expected failure

Run:

mvn -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test

Expected: FAIL because processFileAsync still calls the old two-argument upload method and status handling still overwrites record.fileName.

Task 4: Implement Project Upload Filename Isolation

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java

  • Step 1: Use original record filename when forwarding local uploaded files

In processFileAsync, replace:

UploadFileResponse uploadResponse = lsfxClient.uploadFile(lsfxProjectId, file);

with:

UploadFileResponse uploadResponse = lsfxClient.uploadFile(lsfxProjectId, file, record.getFileName());
  • Step 2: Add source control to status post-processing

Add an overload:

private void processRecordAfterLogIdReady(Long projectId, Integer lsfxProjectId,
                                          CcdiFileUploadRecord record, Integer logId) {
    processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId, false);
}

Change the current method signature to:

private void processRecordAfterLogIdReady(Long projectId, Integer lsfxProjectId,
                                          CcdiFileUploadRecord record, Integer logId,
                                          boolean preserveRecordFileName) {

Then replace the filename update block with:

if (!preserveRecordFileName) {
    String fileName = StringUtils.hasText(logItem.getUploadFileName())
        ? logItem.getUploadFileName()
        : logItem.getDownloadFileName();
    if (StringUtils.hasText(fileName)) {
        record.setFileName(fileName);
    }
}
  • Step 3: Call status post-processing with preservation from local upload

In processFileAsync, replace:

processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId);

with:

processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId, true);

Do not change processPullBankInfoAsync; it should continue to call the four-argument overload and preserve the current pull-bank-info behavior.

  • Step 4: Run service tests

Run:

mvn -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test

Expected: PASS.

  • Step 5: Commit project upload service changes

Check the worktree, stage only task files, then verify staged scope:

git status --short
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 diff --cached --name-status

Expected staged files: only CcdiFileUploadServiceImpl.java and CcdiFileUploadServiceImplTest.java.

git commit -m "修复本地上传流水记录文件名覆盖"

Task 5: Verification, Real Page Check, And Implementation Record

Files:

  • Create: docs/reports/implementation/2026-05-06-bank-upload-original-filename-implementation.md

  • Reference: ruoyi-admin/src/main/resources/application-dev.yml

  • Reference: lsfx-mock-server/routers/api.py

  • Reference: lsfx-mock-server/services/file_service.py

  • Step 1: Run focused backend regression tests

Run:

mvn -pl ccdi-lsfx,ccdi-project -am -Dtest=HttpUtilTest,LsfxAnalysisClientTest,CcdiFileUploadServiceImplTest,CcdiFileUploadControllerTest -Dsurefire.failIfNoSpecifiedTests=false test

Expected: PASS.

  • Step 2: Run compile check for affected modules

Run:

mvn -pl ccdi-lsfx,ccdi-project -am -DskipTests compile

Expected: BUILD SUCCESS.

  • Step 3: Start verification services

Use the project backend restart script:

sh bin/restart_java_backend.sh

For the mock service, use local dev defaults:

cd lsfx-mock-server
python3 main.py --rule-hit-mode subset

If the frontend is not already running, start it with the project Node version:

cd ruoyi-ui
nvm use
npm run dev
  • Step 4: Perform real page upload check with @browser-use:browser

In the actual business page:

  1. Login through the real application.
  2. Open project detail -> 上传数据.
  3. Upload a test file named with a distinctive original name, for example 原始文件名验证-20260506.xlsx.
  4. Confirm the upload record table displays 原始文件名验证-20260506.xlsx.
  5. Confirm the mock LSFX upload response or service log records the same filename. The mock service receives the filename through FastAPI UploadFile.filename, and FileService.upload_file writes it into file_record.file_name.

Expected: page table filename and mock LSFX received filename both match the original uploaded filename.

  • Step 5: Clean test data and stop started processes

Remove the uploaded test record through the existing page delete action if it reached parsed success. If test data was created but cannot be removed from the page, clean only the test row by its unique filename or upload record id after confirming the scope.

Stop only the backend, frontend, or mock-service processes started in Step 3.

  • Step 6: Write implementation record

Create docs/reports/implementation/2026-05-06-bank-upload-original-filename-implementation.md with:

# 上传流水文件原始文件名保持实施记录

## 修改内容

- `ccdi-lsfx` 支持 multipart 文件 part 显式指定 filename。
- `ccdi-project` 本地上传流水文件链路转传流水分析平台时使用初始上传文件名。
- 本地上传链路查询状态后不再使用平台返回文件名覆盖上传记录文件名。
- 拉取本行信息链路保持原有文件名处理行为。

## 影响范围

- 影响项目详情“上传数据”中的本地流水文件上传。
- 不影响历史上传记录。
- 不影响拉取本行信息。
- 不涉及前端源码和数据库结构变更。

## 验证情况

- `mvn -pl ccdi-lsfx,ccdi-project -am -Dtest=HttpUtilTest,LsfxAnalysisClientTest,CcdiFileUploadServiceImplTest,CcdiFileUploadControllerTest -Dsurefire.failIfNoSpecifiedTests=false test`
- `mvn -pl ccdi-lsfx,ccdi-project -am -DskipTests compile`
- 真实页面验证记录页面文件名、mock LSFX 接收 filename、测试数据清理和进程关闭结果。
  • Step 7: Commit final verification record

Check the worktree, stage only the implementation record, then verify staged scope:

git status --short
git add docs/reports/implementation/2026-05-06-bank-upload-original-filename-implementation.md
git diff --cached --name-status

Expected staged file: only the implementation record.

git commit -m "文档: 记录上传流水文件名修复验证"

Final Review Checklist

  • 本地上传流水文件转传 LSFX 时 multipart files part 的 filename 是初始上传文件名。
  • 本地上传记录 file_name 在解析成功或失败后仍是初始上传文件名。
  • 拉取本行信息链路仍按平台返回文件名更新记录,不被本次改动影响。
  • 无数据库结构变更。
  • 无前端源码变更。
  • 实施记录已包含测试、真实页面验证和进程清理结果。
  • 提交前 git status --short.DS_Store 或无关文件。