cisd/docs/plans/2026-03-10-file-upload-fileid-plan.md
2026-03-11 14:23:11 +08:00

252 lines
9.7 KiB
Markdown

# File Upload FileId Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Add a reusable file upload module that stores uploaded files under `/home/tms/uploads`, returns `fileId`, and keeps CISD initialization flowing through existing `fileId` fields.
**Architecture:** Introduce a small `modules/file` module with internal upload and query APIs, one metadata table, and a service that owns fileId generation plus filesystem persistence. Keep the init module unchanged at the API boundary except for sharing the same upload storage root and documenting the front-end workflow.
**Tech Stack:** Spring Boot MVC multipart upload, MyBatis-Plus repository pattern, Flyway SQL migration, JUnit 5
---
### Task 1: Add file upload persistence contract
**Files:**
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V3__create_sys_file_record.sql`
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/file/entity/FileRecordEntity.java`
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/file/repository/FileRecordRepository.java`
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/mapper/file/FileRecordMapper.xml`
- Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/file/FileServiceTest.java`
**Step 1: Write the failing test**
Add a test that expects file metadata to be persisted with:
- generated `fileId`
- original file name
- absolute storage path
- `ACTIVE` status
**Step 2: Run test to verify it fails**
Run: `mvn -q -Dtest=FileServiceTest#shouldPersistUploadedFileMetadata test`
Expected: FAIL because file module classes and table mapping do not exist.
**Step 3: Write minimal implementation**
Implement the entity, mapper/repository, and migration with only the fields needed by upload/query.
**Step 4: Run test to verify it passes**
Run: `mvn -q -Dtest=FileServiceTest#shouldPersistUploadedFileMetadata test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/main/resources/db/migration/V3__create_sys_file_record.sql \
src/main/java/com/cisd/tms/modules/file/entity/FileRecordEntity.java \
src/main/java/com/cisd/tms/modules/file/repository/FileRecordRepository.java \
src/main/resources/mapper/file/FileRecordMapper.xml \
src/test/java/com/cisd/tms/modules/file/FileServiceTest.java
git commit -m "feat: add file upload persistence contract"
```
### Task 2: Implement upload service
**Files:**
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/file/service/FileService.java`
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/file/dto/FileUploadResponse.java`
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/file/dto/FileDetailResponse.java`
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/common/config/properties/FileStorageProperties.java`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/application.yml`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/config/application.yml.example`
- Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/file/FileServiceTest.java`
**Step 1: Write the failing test**
Add tests that assert:
- uploaded file is saved under `<upload-base-dir>/<fileId>/<originalFilename>`
- blank file upload is rejected
- oversize upload is rejected
- returned DTO contains `fileId`, size, storagePath
**Step 2: Run test to verify it fails**
Run: `mvn -q -Dtest=FileServiceTest test`
Expected: FAIL because service and properties do not exist.
**Step 3: Write minimal implementation**
Implement:
- `fileId` generation
- file name sanitization
- directory creation
- file save
- metadata insert
- detail query by `fileId`
**Step 4: Run test to verify it passes**
Run: `mvn -q -Dtest=FileServiceTest test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/main/java/com/cisd/tms/modules/file/service/FileService.java \
src/main/java/com/cisd/tms/modules/file/dto/FileUploadResponse.java \
src/main/java/com/cisd/tms/modules/file/dto/FileDetailResponse.java \
src/main/java/com/cisd/tms/common/config/properties/FileStorageProperties.java \
src/main/resources/application.yml config/application.yml.example \
src/test/java/com/cisd/tms/modules/file/FileServiceTest.java
git commit -m "feat: implement file upload service"
```
### Task 3: Expose internal file APIs
**Files:**
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/file/controller/internal/FileInternalController.java`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/file/FileServiceTest.java`
- Create: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/file/FileInternalControllerTest.java`
**Step 1: Write the failing test**
Add controller tests for:
- `POST /api/v1/files/upload`
- `GET /api/v1/files/{fileId}`
Verify upload uses multipart, returns `ApiResponse`, and query returns stored metadata.
**Step 2: Run test to verify it fails**
Run: `mvn -q -Dtest=FileInternalControllerTest test`
Expected: FAIL because controller does not exist.
**Step 3: Write minimal implementation**
Expose two endpoints only:
- upload
- detail query
Keep them internal-only under `/api/v1/files`.
**Step 4: Run test to verify it passes**
Run: `mvn -q -Dtest=FileInternalControllerTest test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/main/java/com/cisd/tms/modules/file/controller/internal/FileInternalController.java \
src/test/java/com/cisd/tms/modules/file/FileInternalControllerTest.java \
src/test/java/com/cisd/tms/modules/file/FileServiceTest.java
git commit -m "feat: expose internal file upload api"
```
### Task 4: Align init module with shared upload root and docs
**Files:**
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/application.yml`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/config/application.yml.example`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/docs/openapi/cisd-init.openapi.yaml`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/docs/openapi/cisd-init.openapi.json`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/README.md`
- Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java`
**Step 1: Write the failing test**
Add or update tests to assert:
- init executor upload root matches the shared upload root config
- docs clearly state init APIs accept `fileId`, not multipart files
- README documents front-end flow: upload first, then create init task
**Step 2: Run test to verify it fails**
Run: `mvn -q -Dtest=ConfigurableInitStepExecutorTest#shouldDocumentInitFileIdFlow test`
Expected: FAIL because docs/config are not aligned yet.
**Step 3: Write minimal implementation**
Align configuration and docs without changing init request DTO semantics.
**Step 4: Run test to verify it passes**
Run: `mvn -q -Dtest=ConfigurableInitStepExecutorTest#shouldDocumentInitFileIdFlow test`
Expected: PASS
**Step 5: Commit**
```bash
git add src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java \
src/main/resources/application.yml config/application.yml.example \
docs/openapi/cisd-init.openapi.yaml docs/openapi/cisd-init.openapi.json README.md \
src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java
git commit -m "docs: align init fileId flow with upload api"
```
### Task 5: Add upload API OpenAPI spec
**Files:**
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/docs/openapi/cisd-init.openapi.yaml`
- Modify: `/Users/waner/Work/CISD/文档/tms-framework/docs/openapi/cisd-init.openapi.json`
- Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/file/FileInternalControllerTest.java`
**Step 1: Write the failing test**
Add a documentation test or assertion that the upload route and response fields are present in the OpenAPI output.
**Step 2: Run test to verify it fails**
Run: `mvn -q -Dtest=FileInternalControllerTest#shouldDocumentUploadApi test`
Expected: FAIL because the route is not documented yet.
**Step 3: Write minimal implementation**
Document:
- upload API
- query API
- multipart request
- `fileId` response
- init API references to upload-first workflow
**Step 4: Run test to verify it passes**
Run: `mvn -q -Dtest=FileInternalControllerTest#shouldDocumentUploadApi test`
Expected: PASS
**Step 5: Commit**
```bash
git add docs/openapi/cisd-init.openapi.yaml docs/openapi/cisd-init.openapi.json \
src/test/java/com/cisd/tms/modules/file/FileInternalControllerTest.java
git commit -m "docs: add file upload api spec"
```
### Task 6: Final verification
**Files:**
- Verify only
**Step 1: Run file module tests**
Run: `mvn -q -Dtest=FileServiceTest,FileInternalControllerTest test`
Expected: PASS
**Step 2: Run init-related tests**
Run: `mvn -q -Dtest=InitServiceTest,ConfigurableInitStepExecutorTest test`
Expected: PASS
**Step 3: Run compile**
Run: `mvn -q -DskipTests compile`
Expected: PASS
**Step 4: Review focused diff**
Run: `git diff -- src/main/java/com/cisd/tms/modules/file src/main/resources/db/migration/V3__create_sys_file_record.sql src/main/resources/mapper/file src/main/resources/application.yml config/application.yml.example README.md docs/openapi/cisd-init.openapi.yaml docs/openapi/cisd-init.openapi.json`
Expected: only file upload module, config, README, and OpenAPI/doc alignment changes.