# 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", "");
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 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