# Puzzle 拼图模块技术文档

## 📋 模块概述

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

**核心能力：**
- 模板化图片生成：通过模板+元素+动态数据生成定制化图片
- 多层次元素渲染：支持图片和文字元素的分层叠加
- 灵活的样式配置：支持位置、大小、透明度、旋转、圆角等属性
- 动态数据注入：通过elementKey进行动态数据替换
- 智能自动填充：基于规则引擎自动选择和填充素材数据
- 生成记录追踪：完整记录每次生成的参数和结果

**典型应用场景：**
- 订单凭证图片生成（用户头像+订单信息）
- 电子门票生成（二维码+用户信息）
- 电子证书生成（用户信息+证书模板）
- 营销海报生成（动态用户数据）

---

## 🏗️ 架构设计

### 分层结构

```
puzzle/
├── controller/          # API接口层
│   ├── PuzzleGenerateController.java      # 拼图生成接口
│   ├── PuzzleTemplateController.java      # 模板管理接口
│   └── PuzzleFillRuleController.java      # 填充规则管理接口
├── service/            # 业务逻辑层
│   ├── IPuzzleGenerateService.java
│   ├── IPuzzleTemplateService.java
│   ├── IPuzzleFillRuleService.java
│   └── impl/
│       ├── PuzzleGenerateServiceImpl.java
│       ├── PuzzleTemplateServiceImpl.java
│       └── PuzzleFillRuleServiceImpl.java
├── mapper/             # 数据访问层
│   ├── PuzzleTemplateMapper.java
│   ├── PuzzleElementMapper.java
│   ├── PuzzleGenerationRecordMapper.java
│   ├── PuzzleFillRuleMapper.java
│   └── PuzzleFillRuleItemMapper.java
│   # 拼图引擎会复用基础域的 com.ycwl.basic.mapper.SourceMapper（不在 puzzle 包内）
├── entity/             # 实体类
│   ├── PuzzleTemplateEntity.java          # 模板实体
│   ├── PuzzleElementEntity.java           # 元素实体
│   ├── PuzzleGenerationRecordEntity.java  # 生成记录实体
│   ├── PuzzleFillRuleEntity.java          # 填充规则实体
│   └── PuzzleFillRuleItemEntity.java      # 填充规则明细实体
├── dto/                # 数据传输对象
│   ├── PuzzleGenerateRequest.java         # 生成请求
│   ├── PuzzleGenerateResponse.java        # 生成响应
│   ├── PuzzleTemplateDTO.java             # 模板DTO
│   ├── PuzzleElementDTO.java              # 元素DTO
│   ├── TemplateCreateRequest.java         # 模板创建请求
│   ├── ElementCreateRequest.java          # 元素创建请求
│   ├── PuzzleFillRuleSaveRequest.java     # 填充规则保存请求
│   ├── PuzzleFillRuleDTO.java             # 填充规则DTO
│   └── PuzzleFillRuleItemDTO.java         # 填充规则明细DTO
├── fill/               # 自动填充引擎
│   ├── PuzzleElementFillEngine.java       # 填充引擎（核心）
│   ├── condition/                         # 条件策略
│   │   ├── ConditionStrategy.java
│   │   ├── ConditionEvaluator.java
│   │   ├── ConditionContext.java
│   │   ├── AlwaysConditionStrategy.java
│   │   ├── DeviceCountConditionStrategy.java
│   │   ├── DeviceCountRangeConditionStrategy.java
│   │   └── DeviceIdMatchConditionStrategy.java
│   ├── datasource/                        # 数据源解析
│   │   ├── DataSourceResolver.java
│   │   ├── DeviceImageDataSourceStrategy.java
│   │   ├── FaceUrlDataSourceStrategy.java
│   │   └── StaticValueDataSourceStrategy.java
│   └── enums/                             # 枚举定义
│       ├── ConditionType.java
│       ├── DataSourceType.java
│       └── SortStrategy.java
└── util/               # 工具类
    └── PuzzleImageRenderer.java           # 图片渲染引擎（核心）
```

### 设计模式

1. **服务层模式（Service Layer）**：业务逻辑封装在service层，controller只负责接口适配
2. **DTO模式**：使用独立的DTO对象处理API输入输出，与Entity分离
3. **策略模式**：图片适配模式（CONTAIN、COVER、FILL等）、条件匹配策略、数据源解析策略、排序策略
4. **建造者模式**：通过模板+元素配置构建最终图片
5. **责任链模式**：自动填充规则按优先级顺序匹配执行

## 🔐 运行时安全与一致性保证

- **景区隔离强校验**：PuzzleGenerateServiceImpl 会根据模板的 `scenicId` 判断是否允许当前请求生成图片；模板绑定了景区时，请求必须传入相同的 `scenicId`，否则直接拒绝，避免跨租户串用模板。
- **自动填充参数兜底**：自动填充引擎 `PuzzleElementFillEngine` 仅在 `faceId + scenicId` 同时存在时触发，且规则没有明细项时会继续匹配下一条，防止空规则截断后续逻辑。
- **元素类型白名单**：`ElementConfigHelper` 仅允许 `ElementType` 枚举中已经落地的 TEXT、IMAGE 类型入库，杜绝未实现类型绕过验证。
- **图片下载防护**：`ImageElement` 只接受公网 http/https 地址，自动阻断内网、环回以及 file:// 资源，并设置请求超时，缓解 SSRF 与资源阻塞风险。

---

## 🔧 核心组件详解

### 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 - 拼图生成服务

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

**核心方法**：
```java
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. PuzzleElementFillEngine - 自动填充引擎（新功能）

**职责**：基于规则引擎自动选择和填充拼图元素的数据源

**核心概念**：
- 通过配置化的规则（而非硬编码）决定每个元素使用哪些素材
- 支持基于机位数量、机位ID、人脸特征等多维度条件匹配
- 支持多种数据源（机位图片、用户头像、二维码等）
- 支持灵活的排序策略（最新、评分、随机等）
- 支持优先级和降级策略

**核心方法**：
```java
Map<String, String> execute(Long templateId, Long faceId, Long scenicId)
```

**执行流程**：
1. **加载规则列表**：
   - 查询指定模板的所有启用规则（`PuzzleFillRuleMapper.listByTemplateId`）
   - 按`priority`降序排序（优先级高的先执行）

2. **构建上下文**：
   - 查询faceId关联的机位数量（`SourceMapper.countDistinctDevicesByFaceId`）
   - 查询faceId关联的机位ID列表（`SourceMapper.getDeviceIdsByFaceId`）
   - 构建`ConditionContext`对象

3. **规则匹配**：
   - 遍历规则列表，调用`ConditionEvaluator.evaluate()`评估每条规则
   - 匹配到第一条符合条件的规则后停止（责任链模式）

4. **执行填充**：
   - 查询匹配规则的所有明细项（`PuzzleFillRuleItemMapper.listByRuleId`）
   - 按`itemOrder`排序
   - 对每条明细调用`DataSourceResolver.resolve()`解析数据源
   - 返回`Map<elementKey, dataValue>`

**条件策略（Strategy Pattern）**：

| 策略类型 | 类名 | 匹配逻辑 | 配置示例 |
|---------|------|---------|---------|
| 总是匹配 | AlwaysConditionStrategy | 总是返回true，用作兜底规则 | `{}` |
| 机位数量匹配（模式1） | DeviceCountConditionStrategy | 实际机位数 ≥ deviceCount | `{"deviceCount": 4}` |
| 机位数量匹配（模式2） | DeviceCountConditionStrategy | 从指定列表中过滤并匹配数量 ≥ deviceCount，只取前N个，保持配置顺序 | `{"deviceCount": 2, "deviceIds": [200, 300, 400]}` |
| 机位ID匹配 | DeviceIdMatchConditionStrategy | 匹配指定的机位ID（支持ANY/ALL模式） | `{"deviceIds": [200, 300], "matchMode": "ALL"}` |

**数据源类型**：

| 类型 | 说明 | sourceFilter 配置 |
|------|------|------------------|
| DEVICE_IMAGE | 机位图片 | `{"deviceIndex": 0, "type": 2}` - deviceIndex指定使用第几个机位，type指定图片类型 |
| USER_AVATAR | 用户头像 | `{}` |
| QR_CODE | 二维码 | `{"content": "{orderId}"}` - 支持变量替换 |

**排序策略**：

| 策略 | 说明 |
|------|------|
| LATEST | 最新优先（按创建时间降序） |
| EARLIEST | 最早优先（按创建时间升序） |
| SCORE_DESC | 评分降序（适用于有评分的素材） |
| SCORE_ASC | 评分升序 |
| RANDOM | 随机选择 |

**降级策略**：
- 每条明细可配置`fallbackValue`
- 当数据源无法获取到值时，使用降级默认值
- 如果降级值也为空，则跳过该元素的填充

**技术要点**：
- 使用Spring的`@Component`自动注册策略
- 使用Jackson解析JSON配置
- 缓存机位数量和机位列表，单次执行仅查询一次
- 详细日志记录规则匹配和填充过程

**使用场景**：
- 根据机位数量选择不同布局（4机位用4宫格，6机位用六宫格）
- 优先使用高质量机位的图片（指定机位200、300）
- 多机位组合场景（只有机位A和B同时存在时使用特定布局）

**性能优化**：
- 规则数量建议不超过10条/模板
- 优先级高的规则应配置更精确的条件
- 使用`ALWAYS`策略作为兜底，确保总有规则匹配

---

### 5. Controller接口层

#### PuzzleGenerateController
```java
POST /puzzle/generate
```
**功能**：生成拼图图片

**请求体**：`PuzzleGenerateRequest`
```json
{
  "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>`
```json
{
  "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)

---

### 4. puzzle_fill_rule - 拼图填充规则表

| 字段 | 类型 | 说明 |
|-----|------|-----|
| id | BIGINT | 主键ID |
| template_id | BIGINT | 关联的模板ID（外键） |
| rule_name | VARCHAR(100) | 规则名称 |
| condition_type | VARCHAR(50) | 条件类型：DEVICE_COUNT/DEVICE_COUNT_RANGE/DEVICE_ID_MATCH/ALWAYS |
| condition_value | TEXT | 条件配置（JSON格式） |
| priority | INT | 优先级（数值越大越优先） |
| enabled | TINYINT | 是否启用：0-禁用 1-启用 |
| scenic_id | BIGINT | 景区ID（多租户隔离） |
| description | TEXT | 规则描述 |
| create_time | DATETIME | 创建时间 |
| update_time | DATETIME | 更新时间 |
| deleted | TINYINT | 删除标记：0-未删除 1-已删除 |
| deleted_at | DATETIME | 删除时间 |

**索引**：
- KEY `idx_template_scenic` (template_id, scenic_id, deleted)
- KEY `idx_priority` (priority)

**业务逻辑**：
- 规则按`priority`降序排列执行
- 匹配到第一条符合条件的规则后停止
- 建议使用`ALWAYS`类型作为兜底规则（最低优先级）

---

### 5. puzzle_fill_rule_item - 拼图填充规则明细表

| 字段 | 类型 | 说明 |
|-----|------|-----|
| id | BIGINT | 主键ID |
| rule_id | BIGINT | 关联的规则ID（外键） |
| element_key | VARCHAR(50) | 目标元素标识（对应puzzle_element的element_key） |
| data_source | VARCHAR(50) | 数据源类型：DEVICE_IMAGE/USER_AVATAR/QR_CODE等 |
| source_filter | TEXT | 数据源过滤条件（JSON格式） |
| sort_strategy | VARCHAR(50) | 排序策略：LATEST/EARLIEST/SCORE_DESC/SCORE_ASC/RANDOM |
| fallback_value | VARCHAR(500) | 降级默认值（数据源无法获取时使用） |
| item_order | INT | 明细排序（决定执行顺序） |
| create_time | DATETIME | 创建时间 |
| update_time | DATETIME | 更新时间 |
| deleted | TINYINT | 删除标记：0-未删除 1-已删除 |
| deleted_at | DATETIME | 删除时间 |

**索引**：
- KEY `idx_rule_id` (rule_id, deleted)
- KEY `idx_element_key` (element_key)

**业务逻辑**：
- 明细项按`item_order`升序执行
- 每条明细对应一个元素的填充逻辑
- 支持降级策略（fallbackValue）

---

## 🔄 关键业务流程

### 拼图生成完整流程（含自动填充）

```
用户请求 → Controller接收
    ↓
验证templateCode和dynamicData
    ↓
根据templateCode查询模板（含权限校验）
    ↓
根据templateId查询所有元素（按z-index排序）
    ↓
【新增】调用PuzzleElementFillEngine.execute()（自动填充）
    ├─ 查询该模板的所有填充规则（按优先级排序）
    ├─ 构建ConditionContext（机位数量、机位列表等）
    ├─ 遍历规则进行条件匹配
    ├─ 找到匹配规则后，加载其明细列表
    ├─ 对每条明细调用DataSourceResolver解析数据源
    └─ 返回Map<elementKey, dataValue>
    ↓
合并自动填充数据和用户手动数据（用户数据优先级更高）
    ↓
调用PuzzleImageRenderer.render()
    ├─ 创建画布
    ├─ 绘制背景
    ├─ 遍历元素列表
    │   ├─ 图片元素：下载图片 → 缩放/圆角/透明度/旋转 → 绘制
    │   └─ 文字元素：设置字体样式 → 计算布局 → 绘制
    └─ 返回BufferedImage
    ↓
将BufferedImage转换为字节流
    ↓
上传到OSS获取URL
    ↓
创建生成记录（保存参数和结果）
    ↓
返回响应（imageUrl、width、height、fileSize等）
```

### 图片适配模式说明

**CONTAIN（等比缩放适应）**：
- 图片完全显示在区域内
- 保持图片宽高比
- 可能留白

**COVER（等比缩放填充）**：
- 完全覆盖目标区域
- 保持图片宽高比
- 可能裁剪图片

**FILL（拉伸填充）**：
- 完全填充目标区域
- 不保持宽高比
- 可能变形

**SCALE_DOWN（缩小适应）**：
- 类似CONTAIN
- 但不放大图片（仅缩小）

---

## 📦 对外依赖

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（如`setShadow`、`drawRect`等）

### 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缓存模板和元素配置

---

## 📝 示例代码

### 创建模板示例
```java
// 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);
```

### 生成拼图示例
```java
// 调用生成接口
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());
```
