feat: expand EasyCode software factory workflows

This commit is contained in:
王鹏
2026-07-15 12:48:50 +08:00
parent fcfa4374d7
commit 79dea897bc
1108 changed files with 163774 additions and 21593 deletions

View File

@@ -0,0 +1,744 @@
# Real Image Seed Data Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Generated sample SQL stores image URLs that point to real bundled image files in generated runnable projects.
**Architecture:** Keep database values as web resource URLs, not server disk paths. Add one small Java support class that owns deterministic mock image URLs, generated PNG bytes, and generated asset paths. Use that support class from SQL seed generation and from backend ZIP packaging, then update the generated file controller so `/files/mock/image-01.png` can be served safely.
**Tech Stack:** Java 8, Spring Boot generated templates, Velocity templates, ZIP packaging, JUnit 4, Mockito, MySQL seed SQL files.
---
## File Structure
- Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockImageSeedSupport.java`
- Owns `/files/mock/image-XX.png` URLs and generated `uploads/mock/image-XX.png` PNG bytes.
- Create: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MockImageSeedSupportTest.java`
- Verifies generated images are readable PNGs and URL/path numbering is stable.
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockDataSqlBuilder.java`
- Replaces hard-coded `/files/mock/image-N.png` values with `MockImageSeedSupport.imageUrlForRow(row)`.
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MockDataSqlBuilderTest.java`
- Covers image seed URL output.
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java`
- Adds bundled mock images to backend ZIP output beside the generated backend `pom.xml`.
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java`
- Covers backend ZIP entries for `uploads/mock/image-01.png` and `uploads/mock/image-10.png`.
- Modify: `ruoyi-generator/src/main/resources/qing/fileController.java.vm`
- Serves nested paths under `/files/**` and blocks path traversal.
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java`
- Covers the file controller template and SQL seed copies.
- Modify: `sql/qing_templates.sql`
- Syncs the `FileController.java.vm` template seed.
- Modify: `sql/db.sql`
- Syncs the runnable template seed copy.
---
### Task 1: Add Mock Image Seed Support
**Files:**
- Create: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockImageSeedSupport.java`
- Create: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MockImageSeedSupportTest.java`
- [ ] **Step 1: Write the failing tests**
Create `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MockImageSeedSupportTest.java`:
```java
package com.ruoyi.generator.service.front;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import javax.imageio.ImageIO;
import org.junit.Test;
public class MockImageSeedSupportTest
{
@Test
public void imageUrlsAndAssetPathsUseStableTwoDigitNames()
{
assertEquals("/files/mock/image-01.png", MockImageSeedSupport.imageUrlForRow(1));
assertEquals("/files/mock/image-10.png", MockImageSeedSupport.imageUrlForRow(10));
assertEquals("/files/mock/image-01.png", MockImageSeedSupport.imageUrlForRow(11));
assertEquals("uploads/mock/image-01.png", MockImageSeedSupport.imageAssetPathForRow(1));
assertEquals("uploads/mock/image-10.png", MockImageSeedSupport.imageAssetPathForRow(10));
}
@Test
public void createsReadablePngImagesForAllSeedRows() throws Exception
{
for (int row = 1; row <= MockImageSeedSupport.IMAGE_COUNT; row++)
{
byte[] bytes = MockImageSeedSupport.imageBytesForRow(row);
assertTrue("image bytes should not be empty", bytes.length > 1000);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes));
assertNotNull("image should be readable", image);
assertEquals(640, image.getWidth());
assertEquals(360, image.getHeight());
}
}
}
```
- [ ] **Step 2: Run tests to verify failure**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=MockImageSeedSupportTest test
```
Expected: FAIL because `MockImageSeedSupport` does not exist.
- [ ] **Step 3: Add the support class**
Create `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockImageSeedSupport.java`:
```java
package com.ruoyi.generator.service.front;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Locale;
import javax.imageio.ImageIO;
public final class MockImageSeedSupport
{
public static final int IMAGE_COUNT = 10;
private static final int WIDTH = 640;
private static final int HEIGHT = 360;
private static final Color[] BACKGROUNDS = new Color[] {
new Color(44, 123, 229),
new Color(0, 150, 136),
new Color(255, 152, 0),
new Color(103, 58, 183),
new Color(76, 175, 80),
new Color(233, 30, 99),
new Color(63, 81, 181),
new Color(0, 121, 107),
new Color(198, 40, 40),
new Color(69, 90, 100)
};
private MockImageSeedSupport()
{
}
public static String imageUrlForRow(int row)
{
return "/files/mock/image-" + imageNumber(row) + ".png";
}
public static String imageAssetPathForRow(int row)
{
return "uploads/mock/image-" + imageNumber(row) + ".png";
}
public static byte[] imageBytesForRow(int row) throws IOException
{
int index = normalizedIndex(row);
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = image.createGraphics();
try
{
drawImage(graphics, index);
}
finally
{
graphics.dispose();
}
ByteArrayOutputStream output = new ByteArrayOutputStream();
ImageIO.write(image, "png", output);
return output.toByteArray();
}
private static void drawImage(Graphics2D graphics, int index)
{
Color background = BACKGROUNDS[index - 1];
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setColor(background);
graphics.fillRect(0, 0, WIDTH, HEIGHT);
graphics.setColor(new Color(255, 255, 255, 42));
graphics.fillOval(-90, -80, 260, 260);
graphics.fillOval(WIDTH - 210, HEIGHT - 180, 300, 300);
graphics.setStroke(new BasicStroke(3f));
graphics.setColor(new Color(255, 255, 255, 80));
graphics.drawRoundRect(28, 28, WIDTH - 56, HEIGHT - 56, 28, 28);
drawCenteredText(graphics, "Sample Image", new Font(Font.SANS_SERIF, Font.BOLD, 42), HEIGHT / 2 - 22);
drawCenteredText(graphics, imageNumber(index), new Font(Font.SANS_SERIF, Font.BOLD, 72), HEIGHT / 2 + 62);
}
private static void drawCenteredText(Graphics2D graphics, String text, Font font, int baseline)
{
graphics.setFont(font);
FontMetrics metrics = graphics.getFontMetrics();
int x = (WIDTH - metrics.stringWidth(text)) / 2;
graphics.setColor(Color.WHITE);
graphics.drawString(text, x, baseline);
}
private static String imageNumber(int row)
{
return String.format(Locale.ENGLISH, "%02d", normalizedIndex(row));
}
private static int normalizedIndex(int row)
{
int safeRow = Math.max(1, row);
return ((safeRow - 1) % IMAGE_COUNT) + 1;
}
}
```
- [ ] **Step 4: Run tests to verify pass**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=MockImageSeedSupportTest test
```
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockImageSeedSupport.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MockImageSeedSupportTest.java
git commit -m "feat: add bundled mock image seed support"
```
---
### Task 2: Use Real Image URLs in Mock SQL
**Files:**
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MockDataSqlBuilderTest.java`
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockDataSqlBuilder.java`
- [ ] **Step 1: Write the failing test**
Add this test method to `MockDataSqlBuilderTest`:
```java
@Test
public void imageUploadColumnsUseBundledMockImageUrls()
{
DatabaseTableDesign table = new DatabaseTableDesign();
table.setTableName("product");
table.setColumns(Arrays.asList(
column("id", "bigint(20)", "ID", "ID", "1", "1", "input", null, null),
column("cover_url", "varchar(100)", "Cover", "Cover", "0", "0", "imageUpload", null, null),
column("avatar_url", "varchar(100)", "Avatar", "Avatar", "0", "0", "avatar", null, null)));
String sql = builder.build(table);
assertTrue(sql.contains("'/files/mock/image-01.png' as `cover_url`"));
assertTrue(sql.contains("'/files/mock/image-01.png' as `avatar_url`"));
assertTrue(sql.contains("'/files/mock/image-10.png'"));
assertFalse(sql.contains("/files/mock/image-1.png"));
}
```
- [ ] **Step 2: Run test to verify failure**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=MockDataSqlBuilderTest#imageUploadColumnsUseBundledMockImageUrls test
```
Expected: FAIL because the existing SQL contains `/files/mock/image-1.png`.
- [ ] **Step 3: Replace the image branch**
In `MockDataSqlBuilder.valueFor`, replace this branch:
```java
if (StringUtils.inStringIgnoreCase(htmlType, "imageUpload", "avatar"))
{
return quoted(fitTextValue("/files/mock/image-" + row + ".png", column));
}
```
with:
```java
if (StringUtils.inStringIgnoreCase(htmlType, "imageUpload", "avatar"))
{
return quoted(fitTextValue(MockImageSeedSupport.imageUrlForRow(row), column));
}
```
No import is needed because both classes are in `com.ruoyi.generator.service.front`.
- [ ] **Step 4: Run tests to verify pass**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=MockDataSqlBuilderTest,MockImageSeedSupportTest test
```
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockDataSqlBuilder.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/MockDataSqlBuilderTest.java
git commit -m "feat: seed image columns with bundled file urls"
```
---
### Task 3: Package Mock Images into Backend Downloads
**Files:**
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java`
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java`
- [ ] **Step 1: Write the failing ZIP test**
Add this test method near the existing `downloadStructure...` tests in `GenProjectServiceImplTest`:
```java
@Test
public void downloadStructurePackagesMockImageAssetsWithBackend() throws Exception {
GenProject project = project();
project.setFrontDraft(true);
TemplateFile pomTemplate = templateFile("pom.xml.vm", "<project></project>");
pomTemplate.setTemplateId(9201L);
when(templateService.selectTemplateList(any(Template.class))).thenReturn(Arrays.asList(template(9201L, 0L)));
when(sysProjectStructureService.selectSysProjectStructureList(any(SysProjectStructure.class))).thenReturn(Arrays.asList(
structure(1L, 0L, "{projectName}", "folder", null, null),
structure(2L, 1L, "pom.xml", "file", 11L, "pom.xml.vm")
));
when(sysProjectModuleService.selectSysProjectModuleList(any(SysProjectModule.class))).thenReturn(Arrays.asList());
when(codeSnippetService.selectSysCodeSnippetList(any(SysCodeSnippet.class))).thenReturn(Arrays.asList());
when(templateFileService.selectTemplateFilesByTemplateId(9201L)).thenReturn(Arrays.asList(pomTemplate));
byte[] data = service.downloadStructure(project, "backend");
Set<String> entries = zipEntries(data);
assertTrue(entries.contains("demo-app/uploads/mock/image-01.png"));
assertTrue(entries.contains("demo-app/uploads/mock/image-10.png"));
}
```
- [ ] **Step 2: Run test to verify failure**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=GenProjectServiceImplTest#downloadStructurePackagesMockImageAssetsWithBackend test
```
Expected: FAIL because backend ZIPs do not include `uploads/mock/image-01.png`.
- [ ] **Step 3: Add import**
In `GenProjectServiceImpl.java`, add:
```java
import com.ruoyi.generator.service.front.MockImageSeedSupport;
```
- [ ] **Step 4: Replace `processStructure` and add helpers**
Replace the existing `processStructure` method:
```java
private void processStructure(List<Map<String, Object>> structure, ZipOutputStream zip, GenProject project, String type) throws IOException {
processStructureWithPath(structure, zip, project, "", type, new HashSet<String>());
}
```
with:
```java
private void processStructure(List<Map<String, Object>> structure, ZipOutputStream zip, GenProject project, String type) throws IOException {
Set<String> zipEntries = new HashSet<String>();
processStructureWithPath(structure, zip, project, "", type, zipEntries);
appendMockImageAssets(zip, type, zipEntries);
}
private void appendMockImageAssets(ZipOutputStream zip, String type, Set<String> zipEntries) throws IOException {
if (!"backend".equals(type)) {
return;
}
String backendRootPrefix = backendRootPrefix(zipEntries);
if (backendRootPrefix == null) {
return;
}
for (int row = 1; row <= MockImageSeedSupport.IMAGE_COUNT; row++) {
String entryName = backendRootPrefix + MockImageSeedSupport.imageAssetPathForRow(row);
if (zipEntries.add(entryName)) {
zip.putNextEntry(new ZipEntry(entryName));
zip.write(MockImageSeedSupport.imageBytesForRow(row));
zip.closeEntry();
}
}
}
private String backendRootPrefix(Set<String> zipEntries) {
for (String entryName : zipEntries) {
if ("pom.xml".equals(entryName)) {
return "";
}
if (entryName != null && entryName.endsWith("/pom.xml")) {
return entryName.substring(0, entryName.length() - "pom.xml".length());
}
}
return null;
}
```
- [ ] **Step 5: Run focused tests**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=GenProjectServiceImplTest#downloadStructurePackagesMockImageAssetsWithBackend,MockImageSeedSupportTest test
```
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java
git commit -m "feat: package mock images with generated backend"
```
---
### Task 4: Serve Nested Mock Image Paths
**Files:**
- Modify: `ruoyi-generator/src/main/resources/qing/fileController.java.vm`
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java`
- [ ] **Step 1: Write the failing template test**
In `QingTemplateSupportTest`, add this test method near the other runnable Qing template checks:
```java
@Test
public void fileControllerTemplateServesNestedMockImagePathsSafely() throws Exception
{
String template = read(repoRoot().resolve("ruoyi-generator/src/main/resources/qing/fileController.java.vm"));
assertTrue(template.contains("@GetMapping(\"/**\")"));
assertTrue(template.contains("HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE"));
assertTrue(template.contains("Path normalizedRoot = uploadRoot.toAbsolutePath().normalize();"));
assertTrue(template.contains("if (!filePath.startsWith(normalizedRoot))"));
assertFalse(template.contains("@GetMapping(\"/{fileName:.+}\")"));
}
```
- [ ] **Step 2: Run test to verify failure**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=QingTemplateSupportTest#fileControllerTemplateServesNestedMockImagePathsSafely test
```
Expected: FAIL because the template still uses `@GetMapping("/{fileName:.+}")`.
- [ ] **Step 3: Replace the file controller template content**
Replace the complete content of `ruoyi-generator/src/main/resources/qing/fileController.java.vm` with:
```java
package ${packageName}.controller;
import ${packageName}.common.Result;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
@RestController
@RequestMapping("/files")
public class FileController
{
private final Path uploadRoot = Paths.get("uploads");
@PostMapping("/upload")
public Result<Map<String, String>> upload(@RequestParam("file") MultipartFile file) throws IOException
{
Files.createDirectories(uploadRoot);
String originalName = StringUtils.cleanPath(file.getOriginalFilename() == null ? "file" : file.getOriginalFilename());
String suffix = "";
int dotIndex = originalName.lastIndexOf('.');
if (dotIndex >= 0)
{
suffix = originalName.substring(dotIndex);
}
String fileName = UUID.randomUUID().toString().replace("-", "") + suffix;
Files.copy(file.getInputStream(), uploadRoot.resolve(fileName), StandardCopyOption.REPLACE_EXISTING);
Map<String, String> data = new HashMap<String, String>();
data.put("name", originalName);
data.put("url", "/files/" + fileName);
return Result.success(data);
}
@GetMapping("/**")
public ResponseEntity<Resource> getFile(HttpServletRequest request) throws MalformedURLException
{
String requestPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String fileName = cleanStoredPath(requestPath);
if (!StringUtils.hasText(fileName))
{
return ResponseEntity.notFound().build();
}
Path normalizedRoot = uploadRoot.toAbsolutePath().normalize();
Path filePath = normalizedRoot.resolve(fileName).normalize();
if (!filePath.startsWith(normalizedRoot))
{
return ResponseEntity.notFound().build();
}
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists())
{
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
private String cleanStoredPath(String requestPath)
{
String storedPath = requestPath == null ? "" : requestPath;
if (storedPath.startsWith("/files/"))
{
storedPath = storedPath.substring("/files/".length());
}
else if (storedPath.startsWith("/files"))
{
storedPath = storedPath.substring("/files".length());
}
while (storedPath.startsWith("/"))
{
storedPath = storedPath.substring(1);
}
storedPath = StringUtils.cleanPath(storedPath);
return storedPath.contains("..") ? "" : storedPath;
}
}
```
- [ ] **Step 4: Run template test**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=QingTemplateSupportTest#fileControllerTemplateServesNestedMockImagePathsSafely test
```
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add ruoyi-generator/src/main/resources/qing/fileController.java.vm ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java
git commit -m "fix: serve generated mock image subpaths"
```
---
### Task 5: Sync SQL Template Seeds
**Files:**
- Modify: `sql/qing_templates.sql`
- Modify: `sql/db.sql`
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java`
- [ ] **Step 1: Add SQL seed assertions**
In `QingTemplateSupportTest`, add this test method:
```java
@Test
public void sqlSeedFileControllerTemplatesServeNestedMockImagePaths() throws Exception
{
String qingTemplatesSql = read(repoRoot().resolve("sql/qing_templates.sql"));
String dbSql = read(repoRoot().resolve("sql/db.sql"));
assertTrue(qingTemplatesSql.contains("'FileController.java.vm'"));
assertTrue(qingTemplatesSql.contains("@GetMapping(\"/**\")"));
assertTrue(qingTemplatesSql.contains("HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE"));
assertTrue(qingTemplatesSql.contains("if (!filePath.startsWith(normalizedRoot))"));
assertFalse(qingTemplatesSql.contains("@GetMapping(\"/{fileName:.+}\")"));
assertTrue(dbSql.contains("'FileController.java.vm'"));
assertTrue(dbSql.contains("@GetMapping(\"/**\")"));
assertTrue(dbSql.contains("HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE"));
assertTrue(dbSql.contains("if (!filePath.startsWith(normalizedRoot))"));
assertFalse(dbSql.contains("@GetMapping(\"/{fileName:.+}\")"));
}
```
- [ ] **Step 2: Run test to verify failure**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=QingTemplateSupportTest#sqlSeedFileControllerTemplatesServeNestedMockImagePaths test
```
Expected: FAIL because `sql/qing_templates.sql` and `sql/db.sql` still contain the old file controller template.
- [ ] **Step 3: Mechanically sync the SQL template body**
Run this PowerShell command from the repository root:
```powershell
$templatePath = "ruoyi-generator/src/main/resources/qing/fileController.java.vm"
$template = Get-Content -Raw -Encoding UTF8 $templatePath
$escaped = $template.Replace("\", "\\").Replace("'", "''")
foreach ($path in @("sql/qing_templates.sql", "sql/db.sql")) {
$text = Get-Content -Raw -Encoding UTF8 $path
$marker = "(920107, 9201, 'FileController.java.vm', NULL, 'FileController.java.vm', '"
$start = $text.IndexOf($marker)
if ($start -lt 0) { throw "FileController template row not found in $path" }
$contentStart = $start + $marker.Length
$contentEnd = $text.IndexOf("`n', 'admin', sysdate())", $contentStart)
if ($contentEnd -lt 0) { throw "FileController template row end not found in $path" }
$updated = $text.Substring(0, $contentStart) + $escaped + $text.Substring($contentEnd)
Set-Content -Path $path -Value $updated -Encoding UTF8
}
```
- [ ] **Step 4: Run SQL seed test**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=QingTemplateSupportTest#sqlSeedFileControllerTemplatesServeNestedMockImagePaths test
```
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add sql/qing_templates.sql sql/db.sql ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java
git commit -m "chore: sync runnable file controller template seeds"
```
---
### Task 6: Final Verification
**Files:**
- Verify only, no file edits.
- [ ] **Step 1: Run focused Java tests**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=MockImageSeedSupportTest,MockDataSqlBuilderTest,GenProjectServiceImplTest,QingTemplateSupportTest test
```
Expected: PASS.
- [ ] **Step 2: Verify generated SQL values**
Run:
```bash
rg -n "/files/mock/image-0[1-9]\\.png|/files/mock/image-10\\.png|/files/mock/image-[1-9]\\.png" ruoyi-generator/src/main/java ruoyi-generator/src/test/java
```
Expected: output includes the new two-digit paths in tests or assertions, and no production Java code contains `/files/mock/image-` followed by a single digit.
- [ ] **Step 3: Verify SQL seed copies**
Run:
```bash
rg -n "HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE|@GetMapping\\(\"/\\*\\*\"\\)|@GetMapping\\(\"/\\{fileName:\\.\\+\\}\"\\)" ruoyi-generator/src/main/resources/qing/fileController.java.vm sql/qing_templates.sql sql/db.sql
```
Expected: output includes `HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE` and `@GetMapping("/**")`; output does not include `@GetMapping("/{fileName:.+}")`.
- [ ] **Step 4: Verify ZIP contents with the focused test already exercising bytes**
Run:
```bash
mvn -q -pl ruoyi-generator -Dtest=GenProjectServiceImplTest#downloadStructurePackagesMockImageAssetsWithBackend test
```
Expected: PASS, proving backend downloads include `demo-app/uploads/mock/image-01.png` and `demo-app/uploads/mock/image-10.png`.
- [ ] **Step 5: Check whitespace**
Run:
```bash
git diff --check
```
Expected: no output.
- [ ] **Step 6: Review final diff**
Run:
```bash
git diff --stat
git diff -- ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockImageSeedSupport.java ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/MockDataSqlBuilder.java ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java ruoyi-generator/src/main/resources/qing/fileController.java.vm
```
Expected: diff is limited to mock image support, image seed SQL path selection, backend ZIP asset packaging, nested file serving, tests, and SQL template seed copies.
---
## Self-Review
- Spec coverage: sample image columns store `/files/mock/image-XX.png`; generated backend ZIPs contain matching `uploads/mock/image-XX.png` files; generated file controller serves nested `mock/...` resource paths; SQL template seed copies are synchronized.
- Placeholder scan: all code-producing steps include concrete code or concrete replacement snippets.
- Type consistency: `MockImageSeedSupport.imageUrlForRow`, `imageAssetPathForRow`, `imageBytesForRow`, and `IMAGE_COUNT` are used consistently across SQL builder, ZIP packaging, and tests.