refactor(puzzle): 重构元素DTO及新增元素基类

- 将ElementCreateRequest和PuzzleElementDTO中的elementType从Integer改为String
- 删除所有类型特定字段,新增config和configMap支持JSON配置
- 新增BaseElement抽象基类定义元素通用行为
- 添加ElementConfig接口和具体实现类ImageConfig、TextConfig
- 创建ElementFactory工厂类和ElementRegistrar注册器
- 新增ElementType枚举和ElementValidationException异常类
- 实现ImageElement和TextElement具体元素类
- 添加Position位置信息封装类
This commit is contained in:
2025-11-18 08:13:38 +08:00
parent 5c49a5af9e
commit 3d361200b0
28 changed files with 2988 additions and 615 deletions

View File

@@ -0,0 +1,99 @@
package com.ycwl.basic.puzzle.element;
import com.ycwl.basic.puzzle.element.base.BaseElement;
import com.ycwl.basic.puzzle.element.base.ElementFactory;
import com.ycwl.basic.puzzle.element.enums.ElementType;
import com.ycwl.basic.puzzle.element.impl.ImageElement;
import com.ycwl.basic.puzzle.element.impl.TextElement;
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
import com.ycwl.basic.puzzle.test.PuzzleTestDataBuilder;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.*;
/**
* ElementFactory 单元测试
*
* @author Claude
* @since 2025-01-18
*/
@SpringBootTest
class ElementFactoryTest {
@BeforeAll
static void setUp() {
// 确保Element已注册(Spring会自动调用ElementRegistrar)
ElementFactory.register(ElementType.TEXT, TextElement.class);
ElementFactory.register(ElementType.IMAGE, ImageElement.class);
}
@Test
void testCreateTextElement_Success() {
// Given
PuzzleElementEntity entity = PuzzleTestDataBuilder.createTextElement(
1L, "userName", 100, 200, 300, 50, 10,
"测试文字", 24, "#333333"
);
// When
BaseElement element = ElementFactory.create(entity);
// Then
assertNotNull(element);
assertInstanceOf(TextElement.class, element);
assertEquals(ElementType.TEXT, element.getElementType());
assertEquals("userName", element.getElementKey());
assertEquals(100, element.getPosition().getX());
assertEquals(200, element.getPosition().getY());
assertEquals(300, element.getPosition().getWidth());
assertEquals(50, element.getPosition().getHeight());
}
@Test
void testCreateImageElement_Success() {
// Given
PuzzleElementEntity entity = PuzzleTestDataBuilder.createImageElement(
1L, "userAvatar", 50, 100, 100, 100, 5,
"https://example.com/avatar.jpg"
);
// When
BaseElement element = ElementFactory.create(entity);
// Then
assertNotNull(element);
assertInstanceOf(ImageElement.class, element);
assertEquals(ElementType.IMAGE, element.getElementType());
assertEquals("userAvatar", element.getElementKey());
}
@Test
void testCreateElement_InvalidType() {
// Given
PuzzleElementEntity entity = new PuzzleElementEntity();
entity.setElementType("INVALID_TYPE");
entity.setElementKey("test");
entity.setConfig("{}");
// When & Then
assertThrows(IllegalArgumentException.class, () -> ElementFactory.create(entity));
}
@Test
void testCreateElement_NullConfig() {
// Given
PuzzleElementEntity entity = new PuzzleElementEntity();
entity.setElementType("TEXT");
entity.setElementKey("test");
entity.setConfig(null);
entity.setXPosition(0);
entity.setYPosition(0);
entity.setWidth(100);
entity.setHeight(50);
// When & Then
assertThrows(IllegalArgumentException.class, () -> ElementFactory.create(entity));
}
}

View File

@@ -0,0 +1,124 @@
package com.ycwl.basic.puzzle.element;
import com.ycwl.basic.puzzle.element.base.BaseElement;
import com.ycwl.basic.puzzle.element.base.ElementFactory;
import com.ycwl.basic.puzzle.element.exception.ElementValidationException;
import com.ycwl.basic.puzzle.element.renderer.RenderContext;
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
import com.ycwl.basic.puzzle.test.PuzzleTestDataBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
/**
* ImageElement 单元测试
*
* @author Claude
* @since 2025-01-18
*/
class ImageElementTest {
private Graphics2D graphics;
private RenderContext context;
@BeforeEach
void setUp() {
BufferedImage canvas = new BufferedImage(800, 600, BufferedImage.TYPE_INT_ARGB);
graphics = canvas.createGraphics();
Map<String, String> dynamicData = new HashMap<>();
context = new RenderContext(graphics, dynamicData, 800, 600);
}
@Test
void testImageElement_Creation_Success() {
// Given
PuzzleElementEntity entity = PuzzleTestDataBuilder.createImageElement(
1L, "userAvatar", 50, 100, 100, 100, 5,
"https://example.com/avatar.jpg"
);
// When
BaseElement element = ElementFactory.create(entity);
// Then
assertNotNull(element);
assertEquals("userAvatar", element.getElementKey());
}
@Test
void testImageElement_RoundedImage_Success() {
// Given
PuzzleElementEntity entity = PuzzleTestDataBuilder.createRoundedImageElement(
1L, "userAvatar", 50, 100, 100, 100, 5,
"https://example.com/avatar.jpg", 50
);
// When
BaseElement element = ElementFactory.create(entity);
// Then
assertNotNull(element);
// 验证配置包含圆角信息
String schema = element.getConfigSchema();
assertTrue(schema.contains("borderRadius"));
}
@Test
void testImageElement_InvalidConfig_MissingDefaultImageUrl() {
// Given
PuzzleElementEntity entity = new PuzzleElementEntity();
entity.setElementType("IMAGE");
entity.setElementKey("testKey");
entity.setConfig("{\"imageFitMode\":\"FILL\"}"); // 缺少 defaultImageUrl
entity.setXPosition(0);
entity.setYPosition(0);
entity.setWidth(100);
entity.setHeight(100);
// When & Then
assertThrows(ElementValidationException.class, () -> ElementFactory.create(entity));
}
@Test
void testImageElement_InvalidConfig_InvalidBorderRadius() {
// Given
PuzzleElementEntity entity = new PuzzleElementEntity();
entity.setElementType("IMAGE");
entity.setElementKey("testKey");
entity.setConfig("{\"defaultImageUrl\":\"test.jpg\",\"borderRadius\":-1}"); // 非法圆角
entity.setXPosition(0);
entity.setYPosition(0);
entity.setWidth(100);
entity.setHeight(100);
// When & Then
assertThrows(ElementValidationException.class, () -> ElementFactory.create(entity));
}
@Test
void testImageElement_GetConfigSchema() {
// Given
PuzzleElementEntity entity = PuzzleTestDataBuilder.createImageElement(
1L, "userAvatar", 50, 100, 100, 100, 5,
"https://example.com/avatar.jpg"
);
BaseElement element = ElementFactory.create(entity);
// When
String schema = element.getConfigSchema();
// Then
assertNotNull(schema);
assertTrue(schema.contains("defaultImageUrl"));
assertTrue(schema.contains("imageFitMode"));
assertTrue(schema.contains("borderRadius"));
}
}

View File

@@ -0,0 +1,107 @@
package com.ycwl.basic.puzzle.element;
import com.ycwl.basic.puzzle.element.base.BaseElement;
import com.ycwl.basic.puzzle.element.base.ElementFactory;
import com.ycwl.basic.puzzle.element.exception.ElementValidationException;
import com.ycwl.basic.puzzle.element.renderer.RenderContext;
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
import com.ycwl.basic.puzzle.test.PuzzleTestDataBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
/**
* TextElement 单元测试
*
* @author Claude
* @since 2025-01-18
*/
class TextElementTest {
private Graphics2D graphics;
private RenderContext context;
@BeforeEach
void setUp() {
BufferedImage canvas = new BufferedImage(800, 600, BufferedImage.TYPE_INT_ARGB);
graphics = canvas.createGraphics();
Map<String, String> dynamicData = new HashMap<>();
context = new RenderContext(graphics, dynamicData, 800, 600);
}
@Test
void testTextElement_Render_Success() {
// Given
PuzzleElementEntity entity = PuzzleTestDataBuilder.createTextElement(
1L, "userName", 100, 200, 300, 50, 10,
"默认文字", 24, "#333333"
);
BaseElement element = ElementFactory.create(entity);
// 添加动态数据
context.getDynamicData().put("userName", "张三");
// When & Then (不抛出异常即为成功)
assertDoesNotThrow(() -> element.render(context));
}
@Test
void testTextElement_InvalidConfig_MissingDefaultText() {
// Given
PuzzleElementEntity entity = new PuzzleElementEntity();
entity.setElementType("TEXT");
entity.setElementKey("testKey");
entity.setConfig("{\"fontSize\":14,\"fontColor\":\"#000000\"}"); // 缺少 defaultText
entity.setXPosition(0);
entity.setYPosition(0);
entity.setWidth(100);
entity.setHeight(50);
// When & Then
assertThrows(ElementValidationException.class, () -> ElementFactory.create(entity));
}
@Test
void testTextElement_InvalidConfig_InvalidFontSize() {
// Given
PuzzleElementEntity entity = new PuzzleElementEntity();
entity.setElementType("TEXT");
entity.setElementKey("testKey");
entity.setConfig("{\"defaultText\":\"test\",\"fontSize\":0,\"fontColor\":\"#000000\"}"); // 非法字号
entity.setXPosition(0);
entity.setYPosition(0);
entity.setWidth(100);
entity.setHeight(50);
// When & Then
assertThrows(ElementValidationException.class, () -> ElementFactory.create(entity));
}
@Test
void testTextElement_GetConfigSchema() {
// Given
PuzzleElementEntity entity = PuzzleTestDataBuilder.createTextElement(
1L, "userName", 100, 200, 300, 50, 10,
"测试文字", 24, "#333333"
);
BaseElement element = ElementFactory.create(entity);
// When
String schema = element.getConfigSchema();
// Then
assertNotNull(schema);
assertTrue(schema.contains("defaultText"));
assertTrue(schema.contains("fontSize"));
assertTrue(schema.contains("fontColor"));
}
}