Files
FrameTour-BE/src/main/java/com/ycwl/basic/puzzle/claude.md
Jerry Yan 3d361200b0 refactor(puzzle): 重构元素DTO及新增元素基类
- 将ElementCreateRequest和PuzzleElementDTO中的elementType从Integer改为String
- 删除所有类型特定字段,新增config和configMap支持JSON配置
- 新增BaseElement抽象基类定义元素通用行为
- 添加ElementConfig接口和具体实现类ImageConfig、TextConfig
- 创建ElementFactory工厂类和ElementRegistrar注册器
- 新增ElementType枚举和ElementValidationException异常类
- 实现ImageElement和TextElement具体元素类
- 添加Position位置信息封装类
2025-11-18 08:13:38 +08:00

20 KiB

Puzzle 拼图模块技术文档

📋 模块概述

Puzzle拼图模块是一个基于模板和元素的动态图片生成系统,支持按照预定义的模板配置,将动态数据渲染成最终的图片输出。常用于订单凭证、门票、证书等场景的图片生成。

核心能力:

  • 模板化图片生成:通过模板+元素+动态数据生成定制化图片
  • 多层次元素渲染:支持图片和文字元素的分层叠加
  • 灵活的样式配置:支持位置、大小、透明度、旋转、圆角等属性
  • 动态数据注入:通过elementKey进行动态数据替换
  • 生成记录追踪:完整记录每次生成的参数和结果

典型应用场景:

  • 订单凭证图片生成(用户头像+订单信息)
  • 电子门票生成(二维码+用户信息)
  • 电子证书生成(用户信息+证书模板)
  • 营销海报生成(动态用户数据)

🏗️ 架构设计

分层结构

puzzle/
├── controller/          # API接口层
│   ├── PuzzleGenerateController.java      # 拼图生成接口
│   └── PuzzleTemplateController.java      # 模板管理接口
├── service/            # 业务逻辑层
│   ├── IPuzzleGenerateService.java
│   ├── IPuzzleTemplateService.java
│   └── impl/
│       ├── PuzzleGenerateServiceImpl.java
│       └── PuzzleTemplateServiceImpl.java
├── mapper/             # 数据访问层
│   ├── PuzzleTemplateMapper.java
│   ├── PuzzleElementMapper.java
│   └── PuzzleGenerationRecordMapper.java
├── entity/             # 实体类
│   ├── PuzzleTemplateEntity.java          # 模板实体
│   ├── PuzzleElementEntity.java           # 元素实体
│   └── PuzzleGenerationRecordEntity.java  # 生成记录实体
├── dto/                # 数据传输对象
│   ├── PuzzleGenerateRequest.java         # 生成请求
│   ├── PuzzleGenerateResponse.java        # 生成响应
│   ├── PuzzleTemplateDTO.java             # 模板DTO
│   ├── PuzzleElementDTO.java              # 元素DTO
│   ├── TemplateCreateRequest.java         # 模板创建请求
│   └── ElementCreateRequest.java          # 元素创建请求
└── util/               # 工具类
    └── PuzzleImageRenderer.java           # 图片渲染引擎(核心)

设计模式

  1. 服务层模式(Service Layer):业务逻辑封装在service层,controller只负责接口适配
  2. DTO模式:使用独立的DTO对象处理API输入输出,与Entity分离
  3. 策略模式:图片适配模式(CONTAIN、COVER、FILL等)
  4. 建造者模式:通过模板+元素配置构建最终图片

🔧 核心组件详解

1. PuzzleImageRenderer - 图片渲染引擎

职责:核心渲染引擎,负责将模板配置和元素数据渲染成最终图片

关键方法

  • render(PuzzleTemplateEntity, List<PuzzleElementEntity>, Map<String, String>):主渲染方法
    • 创建画布(根据模板宽高)
    • 绘制背景(纯色或图片背景)
    • 按z-index顺序绘制元素
    • 返回BufferedImage对象

渲染流程

  1. 创建画布:根据模板的canvasWidth和canvasHeight创建BufferedImage
  2. 绘制背景:
    • backgroundType=0:绘制纯色背景(backgroundColor)
    • backgroundType=1:加载并绘制背景图片(backgroundImage)
  3. 按z-index排序元素列表(升序,确保层级正确)
  4. 逐个绘制元素:
    • elementType=1(图片元素):
      • 获取动态数据(dynamicData.get(elementKey))或使用defaultImageUrl
      • 下载图片
      • 根据imageFitMode缩放图片(CONTAIN/COVER/FILL/SCALE_DOWN)
      • 应用borderRadius(圆角)
      • 应用opacity(透明度)
      • 应用rotation(旋转)
      • 绘制到画布指定位置(xPosition, yPosition, width, height)
    • elementType=2(文字元素):
      • 获取动态数据或使用defaultText
      • 设置字体(fontFamily, fontSize, fontWeight, fontStyle)
      • 设置颜色(fontColor)
      • 应用textAlign(对齐方式)
      • 应用lineHeight(行高)
      • 处理maxLines(最大行数截断)
      • 应用textDecoration(下划线/删除线)
      • 应用opacity和rotation
      • 绘制到画布

技术要点

  • 使用Java AWT进行图形绘制
  • 使用Hutool工具库处理图片下载和基础操作
  • 支持图片圆角(通过Ellipse2D.Float或RoundRectangle2D.Float实现clip)
  • 支持透明度(通过AlphaComposite实现)
  • 支持旋转(通过Graphics2D.rotate)

2. PuzzleGenerateServiceImpl - 拼图生成服务

职责:协调拼图生成的完整流程

核心方法

PuzzleGenerateResponse generate(PuzzleGenerateRequest request)

生成流程

  1. 参数校验
    • 校验templateCode是否提供
    • 检查dynamicData是否为空
  2. 加载模板
    • 根据templateCode查询模板(PuzzleTemplateMapper)
    • 检查模板是否存在且启用(status=1)
    • 检查多租户权限(scenicId匹配)
  3. 加载元素
    • 根据templateId查询所有元素(PuzzleElementMapper)
    • 按z-index升序排序
    • 过滤未删除的元素(deleted=0)
  4. 调用渲染引擎
    • 调用PuzzleImageRenderer.render()
    • 传入模板、元素列表、动态数据
  5. 上传图片
    • 将BufferedImage转换为字节流
    • 估算文件大小
    • 上传到对象存储(OSS)
    • 获取图片URL
  6. 创建生成记录
    • 保存到puzzle_generation_record表
    • 记录参数、结果、耗时等信息
  7. 返回响应
    • 返回图片URL、宽高、文件大小等信息

辅助方法

  • createRecord():创建生成记录
  • uploadImage():上传图片到OSS
  • estimateFileSize():估算文件大小

3. PuzzleTemplateServiceImpl - 模板管理服务

职责:管理拼图模板和元素的CRUD操作

模板管理方法

  • createTemplate(TemplateCreateRequest):创建模板
  • updateTemplate(Long, TemplateCreateRequest):更新模板
  • deleteTemplate(Long):逻辑删除模板(软删除)
  • getTemplateDetail(Long):查询模板详情(包含元素列表)
  • getTemplateByCode(String):根据code查询模板
  • listTemplates(Long, String, Integer):分页查询模板列表

元素管理方法

  • addElement(ElementCreateRequest):添加单个元素
  • batchAddElements(Long, List<ElementCreateRequest>):批量添加元素
  • updateElement(Long, ElementCreateRequest):更新元素
  • deleteElement(Long):逻辑删除元素
  • getElementDetail(Long):查询元素详情

业务逻辑要点

  • 删除模板时会级联删除关联的所有元素
  • 支持多租户隔离(根据scenicId)
  • 支持按category分类查询
  • 支持按status过滤启用/禁用的模板

4. Controller接口层

PuzzleGenerateController

POST /puzzle/generate

功能:生成拼图图片

请求体PuzzleGenerateRequest

{
  "templateCode": "order_certificate",
  "userId": 123,
  "orderId": "ORDER20250117001",
  "businessType": "order",
  "scenicId": 1,
  "dynamicData": {
    "userAvatar": "https://example.com/avatar.jpg",
    "userName": "张三",
    "orderNumber": "ORDER20250117001",
    "qrCode": "https://example.com/qr.png"
  },
  "outputFormat": "PNG",
  "quality": 90
}

响应AjaxResult<PuzzleGenerateResponse>

{
  "code": 200,
  "msg": "生成成功",
  "data": {
    "imageUrl": "https://oss.example.com/puzzle/xxx.png",
    "width": 750,
    "height": 1334,
    "fileSize": 245678,
    "recordId": 12345
  }
}

PuzzleTemplateController

提供模板和元素的完整CRUD接口(详见Controller类)


📊 数据模型

1. puzzle_template - 拼图模板表

字段 类型 说明
id BIGINT 主键ID
name VARCHAR(100) 模板名称
code VARCHAR(50) 模板编码(唯一,用于API调用)
canvas_width INT 画布宽度(像素)
canvas_height INT 画布高度(像素)
background_type TINYINT 背景类型:0-纯色 1-图片
background_color VARCHAR(20) 背景颜色(hex格式,如#FFFFFF)
background_image VARCHAR(500) 背景图片URL
description TEXT 模板描述
category VARCHAR(50) 模板分类(order/ticket/certificate等)
status TINYINT 状态:0-禁用 1-启用
scenic_id BIGINT 景区ID(多租户隔离)
create_time DATETIME 创建时间
update_time DATETIME 更新时间
deleted TINYINT 删除标记:0-未删除 1-已删除
deleted_at DATETIME 删除时间

索引

  • UNIQUE KEY uk_code (code, deleted)
  • KEY idx_scenic_id (scenic_id)
  • KEY idx_category_status (category, status, deleted)

2. puzzle_element - 拼图元素表

字段 类型 说明
id BIGINT 主键ID
template_id BIGINT 模板ID(外键)
element_type TINYINT 元素类型:1-图片 2-文字
element_key VARCHAR(50) 元素标识(用于动态数据映射)
element_name VARCHAR(100) 元素名称(便于管理)

位置和布局属性

字段 类型 说明
x_position INT X坐标(相对画布左上角)
y_position INT Y坐标(相对画布左上角)
width INT 宽度(像素)
height INT 高度(像素)
z_index INT 层级(数值越大越靠上)
rotation INT 旋转角度(0-360度,顺时针)
opacity INT 不透明度(0-100)

图片元素专有属性

字段 类型 说明
default_image_url VARCHAR(500) 默认图片URL
image_fit_mode VARCHAR(20) 图片适配模式:CONTAIN/COVER/FILL/SCALE_DOWN
border_radius INT 圆角半径(像素)

文字元素专有属性

字段 类型 说明
default_text TEXT 默认文本内容
font_family VARCHAR(50) 字体名称
font_size INT 字号(像素)
font_color VARCHAR(20) 字体颜色(hex)
font_weight VARCHAR(20) 字重:NORMAL/BOLD
font_style VARCHAR(20) 字体样式:NORMAL/ITALIC
text_align VARCHAR(20) 对齐方式:LEFT/CENTER/RIGHT
line_height DECIMAL(3,2) 行高倍数(如1.5)
max_lines INT 最大行数(NULL表示不限制)
text_decoration VARCHAR(20) 文本装饰:NONE/UNDERLINE/LINE_THROUGH

索引

  • KEY idx_template_id (template_id, deleted)
  • KEY idx_element_key (element_key)

3. puzzle_generation_record - 拼图生成记录表

字段 类型 说明
id BIGINT 主键ID
template_id BIGINT 模板ID
template_code VARCHAR(50) 模板编码(冗余)
user_id BIGINT 用户ID
order_id VARCHAR(50) 关联订单号
business_type VARCHAR(50) 业务类型
generation_params TEXT 生成参数(JSON格式)
result_image_url VARCHAR(500) 生成的图片URL
result_file_size BIGINT 文件大小(字节)
result_width INT 图片宽度
result_height INT 图片高度
status TINYINT 状态:0-生成中 1-成功 2-失败
error_message TEXT 错误信息(失败时)
generation_duration INT 生成耗时(毫秒)
retry_count INT 重试次数
scenic_id BIGINT 景区ID
client_ip VARCHAR(50) 客户端IP
user_agent VARCHAR(500) 客户端User-Agent
create_time DATETIME 创建时间
update_time DATETIME 更新时间

索引

  • KEY idx_user_id (user_id)
  • KEY idx_order_id (order_id)
  • KEY idx_template_id (template_id)
  • KEY idx_create_time (create_time)

🔄 关键业务流程

拼图生成完整流程

用户请求 → Controller接收
    ↓
验证templateCode和dynamicData
    ↓
根据templateCode查询模板(含权限校验)
    ↓
根据templateId查询所有元素(按z-index排序)
    ↓
调用PuzzleImageRenderer.render()
    ├─ 创建画布
    ├─ 绘制背景
    ├─ 遍历元素列表
    │   ├─ 图片元素:下载图片 → 缩放/圆角/透明度/旋转 → 绘制
    │   └─ 文字元素:设置字体样式 → 计算布局 → 绘制
    └─ 返回BufferedImage
    ↓
将BufferedImage转换为字节流
    ↓
上传到OSS获取URL
    ↓
创建生成记录(保存参数和结果)
    ↓
返回响应(imageUrl、width、height、fileSize等)

图片适配模式说明

CONTAIN(等比缩放适应)

  • 图片完全显示在区域内
  • 保持图片宽高比
  • 可能留白

COVER(等比缩放填充)

  • 完全覆盖目标区域
  • 保持图片宽高比
  • 可能裁剪图片

FILL(拉伸填充)

  • 完全填充目标区域
  • 不保持宽高比
  • 可能变形

SCALE_DOWN(缩小适应)

  • 类似CONTAIN
  • 但不放大图片(仅缩小)

🛠️ 技术栈

核心依赖

  • Spring Boot:框架基础
  • MyBatis Plus:数据访问
  • Lombok:减少样板代码
  • Hutool:工具类库(图片处理、HTTP下载)
  • Java AWT/ImageIO:图形绘制和图片处理
  • SLF4J/Logback:日志

外部依赖

  • OSS对象存储:图片上传和存储
  • MySQL:关系型数据库

📦 对外依赖

puzzle模块与其他模块的依赖关系:

依赖模块 依赖项 用途
storage OSS上传服务 上传生成的图片到对象存储
config 全局配置 获取系统配置信息
exception 自定义异常 业务异常处理
utils 工具类 通用工具方法

被依赖情况

  • order模块:订单凭证图片生成
  • ticket模块:电子门票图片生成
  • 其他需要动态图片生成的业务模块

🚀 扩展指南

1. 新增元素类型

如需支持新的元素类型(如二维码元素、形状元素等):

  1. PuzzleElementEntity中新增elementType枚举值
  2. PuzzleImageRenderer.render()中添加新类型的渲染逻辑
  3. 新增元素专有属性到puzzle_element表和实体类
  4. 更新DTO和请求对象

2. 新增图片格式支持

当前支持PNG和JPEG,如需支持WebP、SVG等:

  1. 更新PuzzleGenerateRequest.outputFormat校验逻辑
  2. 修改PuzzleGenerateServiceImpl.uploadImage()中的格式转换逻辑
  3. 注意浏览器兼容性

3. 新增渲染效果

如需支持阴影、边框、渐变等效果:

  1. PuzzleElementEntity中新增对应的属性字段
  2. PuzzleImageRenderer中实现对应的绘制逻辑
  3. 使用Java AWT的相关API(如setShadowdrawRect等)

4. 批量生成优化

如需支持批量生成(如批量生成门票):

  1. 新增批量生成接口POST /puzzle/batchGenerate
  2. 使用线程池并发处理
  3. 返回任务ID,支持异步查询结果

⚠️ 重要注意事项

1. 性能优化

  • 图片缓存:对于默认图片URL,考虑使用本地缓存避免重复下载
  • 并发控制:高并发场景下,生成接口应加限流保护
  • 资源释放:及时释放BufferedImage和Graphics2D对象,避免内存泄漏
  • 异步处理:对于复杂模板,考虑异步生成+回调通知

2. 安全性

  • URL校验:对dynamicData中的图片URL进行白名单校验,防止SSRF攻击
  • 文件大小限制:限制下载图片的大小,防止资源耗尽
  • 权限控制:确保scenicId隔离,防止越权访问
  • 输入校验:严格校验所有输入参数,防止XSS和注入攻击

3. 多租户隔离

  • 所有查询必须带上scenicId条件
  • 创建模板和元素时必须关联正确的scenicId
  • 生成拼图时校验模板的scenicId权限

4. 错误处理

  • 图片下载失败时的降级策略(使用默认图片)
  • 渲染失败时记录详细错误日志
  • 对外暴露友好的错误提示

5. 字体问题

  • 字体文件:确保服务器安装了模板使用的字体文件
  • 中文字体:Linux服务器需要安装中文字体(如文泉驿)
  • 字体回退:设置字体回退机制,避免乱码

6. 数据一致性

  • 删除模板时级联删除元素(软删除)
  • 更新模板状态时考虑对正在生成的任务的影响
  • 生成记录不可删除,仅供审计和统计

7. 监控和日志

  • 记录每次生成的耗时,监控性能
  • 记录生成失败的详细原因,便于排查
  • 统计各模板的使用频率,优化热点模板

📈 性能指标参考

典型性能数据(测试环境):

  • 单张简单拼图(2-3个元素):< 500ms
  • 单张复杂拼图(10+个元素):< 1500ms
  • 图片下载耗时:200-500ms(取决于网络)
  • 渲染耗时:50-200ms
  • OSS上传耗时:100-300ms

优化建议

  • 使用CDN加速图片下载
  • 预热常用模板的背景图片
  • 使用Redis缓存模板和元素配置

📝 示例代码

创建模板示例

// 1. 创建模板
TemplateCreateRequest templateReq = new TemplateCreateRequest();
templateReq.setName("订单凭证模板");
templateReq.setCode("order_certificate_v1");
templateReq.setCanvasWidth(750);
templateReq.setCanvasHeight(1334);
templateReq.setBackgroundType(1);
templateReq.setBackgroundImage("https://oss.example.com/bg.jpg");
templateReq.setCategory("order");
templateReq.setScenicId(1L);

Long templateId = templateService.createTemplate(templateReq);

// 2. 添加元素 - 用户头像(图片元素)
ElementCreateRequest avatarElement = new ElementCreateRequest();
avatarElement.setTemplateId(templateId);
avatarElement.setElementType(1); // 图片
avatarElement.setElementKey("userAvatar");
avatarElement.setElementName("用户头像");
avatarElement.setXPosition(50);
avatarElement.setYPosition(100);
avatarElement.setWidth(100);
avatarElement.setHeight(100);
avatarElement.setZIndex(10);
avatarElement.setDefaultImageUrl("https://oss.example.com/default-avatar.png");
avatarElement.setImageFitMode("COVER");
avatarElement.setBorderRadius(50); // 圆形头像
avatarElement.setOpacity(100);

templateService.addElement(avatarElement);

// 3. 添加元素 - 用户名(文字元素)
ElementCreateRequest nameElement = new ElementCreateRequest();
nameElement.setTemplateId(templateId);
nameElement.setElementType(2); // 文字
nameElement.setElementKey("userName");
nameElement.setElementName("用户名");
nameElement.setXPosition(170);
nameElement.setYPosition(120);
nameElement.setWidth(300);
nameElement.setHeight(60);
nameElement.setZIndex(20);
nameElement.setDefaultText("用户名");
nameElement.setFontFamily("PingFang SC");
nameElement.setFontSize(28);
nameElement.setFontColor("#333333");
nameElement.setFontWeight("BOLD");
nameElement.setTextAlign("LEFT");
nameElement.setLineHeight(new BigDecimal("1.5"));

templateService.addElement(nameElement);

生成拼图示例

// 调用生成接口
PuzzleGenerateRequest request = new PuzzleGenerateRequest();
request.setTemplateCode("order_certificate_v1");
request.setUserId(123L);
request.setOrderId("ORDER20250117001");
request.setBusinessType("order");
request.setScenicId(1L);

Map<String, String> dynamicData = new HashMap<>();
dynamicData.put("userAvatar", "https://oss.example.com/user123/avatar.jpg");
dynamicData.put("userName", "张三");
dynamicData.put("orderNumber", "ORDER20250117001");
dynamicData.put("qrCode", "https://oss.example.com/qr/ORDER20250117001.png");
request.setDynamicData(dynamicData);

request.setOutputFormat("PNG");
request.setQuality(90);

PuzzleGenerateResponse response = generateService.generate(request);
System.out.println("生成成功,图片URL: " + response.getImageUrl());

🔗 相关文档


📞 联系方式

如有问题或建议,请联系模块负责人或提交Issue。

维护者:Claude 创建时间:2025-01-17 最后更新:2025-01-17