You've already forked FrameTour-BE
feat(puzzle): 实现智能自动填充引擎和安全增强
- 新增拼图元素自动填充引擎 PuzzleElementFillEngine - 支持基于规则的条件匹配和数据源解析 - 实现机位数量、机位ID等多维度条件策略 - 添加 DEVICE_IMAGE、USER_AVATAR 等数据源类型支持 - 增加景区隔离校验确保模板使用安全性 - 强化图片下载安全校验,防范 SSRF 攻击 - 支持本地文件路径解析和公网 URL 安全检查 - 完善静态值数据源策略支持 localPath 配置 - 优化生成流程中 faceId 和 scenicId 的校验逻辑 - 补充相关单元测试覆盖核心功能点
This commit is contained in:
@@ -9,6 +9,7 @@ Puzzle拼图模块是一个基于模板和元素的动态图片生成系统,
|
||||
- 多层次元素渲染:支持图片和文字元素的分层叠加
|
||||
- 灵活的样式配置:支持位置、大小、透明度、旋转、圆角等属性
|
||||
- 动态数据注入:通过elementKey进行动态数据替换
|
||||
- 智能自动填充:基于规则引擎自动选择和填充素材数据
|
||||
- 生成记录追踪:完整记录每次生成的参数和结果
|
||||
|
||||
**典型应用场景:**
|
||||
@@ -27,28 +28,58 @@ Puzzle拼图模块是一个基于模板和元素的动态图片生成系统,
|
||||
puzzle/
|
||||
├── controller/ # API接口层
|
||||
│ ├── PuzzleGenerateController.java # 拼图生成接口
|
||||
│ └── PuzzleTemplateController.java # 模板管理接口
|
||||
│ ├── PuzzleTemplateController.java # 模板管理接口
|
||||
│ └── PuzzleFillRuleController.java # 填充规则管理接口
|
||||
├── service/ # 业务逻辑层
|
||||
│ ├── IPuzzleGenerateService.java
|
||||
│ ├── IPuzzleTemplateService.java
|
||||
│ ├── IPuzzleFillRuleService.java
|
||||
│ └── impl/
|
||||
│ ├── PuzzleGenerateServiceImpl.java
|
||||
│ └── PuzzleTemplateServiceImpl.java
|
||||
│ ├── PuzzleTemplateServiceImpl.java
|
||||
│ └── PuzzleFillRuleServiceImpl.java
|
||||
├── mapper/ # 数据访问层
|
||||
│ ├── PuzzleTemplateMapper.java
|
||||
│ ├── PuzzleElementMapper.java
|
||||
│ └── PuzzleGenerationRecordMapper.java
|
||||
│ ├── PuzzleGenerationRecordMapper.java
|
||||
│ ├── PuzzleFillRuleMapper.java
|
||||
│ └── PuzzleFillRuleItemMapper.java
|
||||
│ # 拼图引擎会复用基础域的 com.ycwl.basic.mapper.SourceMapper(不在 puzzle 包内)
|
||||
├── entity/ # 实体类
|
||||
│ ├── PuzzleTemplateEntity.java # 模板实体
|
||||
│ ├── PuzzleElementEntity.java # 元素实体
|
||||
│ └── PuzzleGenerationRecordEntity.java # 生成记录实体
|
||||
│ ├── PuzzleGenerationRecordEntity.java # 生成记录实体
|
||||
│ ├── PuzzleFillRuleEntity.java # 填充规则实体
|
||||
│ └── PuzzleFillRuleItemEntity.java # 填充规则明细实体
|
||||
├── dto/ # 数据传输对象
|
||||
│ ├── PuzzleGenerateRequest.java # 生成请求
|
||||
│ ├── PuzzleGenerateResponse.java # 生成响应
|
||||
│ ├── PuzzleTemplateDTO.java # 模板DTO
|
||||
│ ├── PuzzleElementDTO.java # 元素DTO
|
||||
│ ├── TemplateCreateRequest.java # 模板创建请求
|
||||
│ └── ElementCreateRequest.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 # 图片渲染引擎(核心)
|
||||
```
|
||||
@@ -57,8 +88,16 @@ puzzle/
|
||||
|
||||
1. **服务层模式(Service Layer)**:业务逻辑封装在service层,controller只负责接口适配
|
||||
2. **DTO模式**:使用独立的DTO对象处理API输入输出,与Entity分离
|
||||
3. **策略模式**:图片适配模式(CONTAIN、COVER、FILL等)
|
||||
3. **策略模式**:图片适配模式(CONTAIN、COVER、FILL等)、条件匹配策略、数据源解析策略、排序策略
|
||||
4. **建造者模式**:通过模板+元素配置构建最终图片
|
||||
5. **责任链模式**:自动填充规则按优先级顺序匹配执行
|
||||
|
||||
## 🔐 运行时安全与一致性保证
|
||||
|
||||
- **景区隔离强校验**:PuzzleGenerateServiceImpl 会根据模板的 `scenicId` 判断是否允许当前请求生成图片;模板绑定了景区时,请求必须传入相同的 `scenicId`,否则直接拒绝,避免跨租户串用模板。
|
||||
- **自动填充参数兜底**:自动填充引擎 `PuzzleElementFillEngine` 仅在 `faceId + scenicId` 同时存在时触发,且规则没有明细项时会继续匹配下一条,防止空规则截断后续逻辑。
|
||||
- **元素类型白名单**:`ElementConfigHelper` 仅允许 `ElementType` 枚举中已经落地的 TEXT、IMAGE 类型入库,杜绝未实现类型绕过验证。
|
||||
- **图片下载防护**:`ImageElement` 只接受公网 http/https 地址,自动阻断内网、环回以及 file:// 资源,并设置请求超时,缓解 SSRF 与资源阻塞风险。
|
||||
|
||||
---
|
||||
|
||||
@@ -179,7 +218,93 @@ PuzzleGenerateResponse generate(PuzzleGenerateRequest request)
|
||||
|
||||
---
|
||||
|
||||
### 4. Controller接口层
|
||||
### 4. PuzzleElementFillEngine - 自动填充引擎(新功能)
|
||||
|
||||
**职责**:基于规则引擎自动选择和填充拼图元素的数据源
|
||||
|
||||
**核心概念**:
|
||||
- 通过配置化的规则(而非硬编码)决定每个元素使用哪些素材
|
||||
- 支持基于机位数量、机位ID、人脸特征等多维度条件匹配
|
||||
- 支持多种数据源(机位图片、用户头像、二维码等)
|
||||
- 支持灵活的排序策略(最新、评分、随机等)
|
||||
- 支持优先级和降级策略
|
||||
|
||||
**核心方法**:
|
||||
```java
|
||||
Map<String, String> execute(Long templateId, Long faceId, Long scenicId)
|
||||
```
|
||||
|
||||
**执行流程**:
|
||||
1. **加载规则列表**:
|
||||
- 查询指定模板和景区的所有启用规则(`PuzzleFillRuleMapper.listByTemplateAndScenic`)
|
||||
- 按`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,用作兜底规则 | `{}` |
|
||||
| 机位数量匹配 | DeviceCountConditionStrategy | 精确匹配机位数量 | `{"deviceCount": 4}` |
|
||||
| 机位数量范围 | DeviceCountRangeConditionStrategy | 机位数量在指定范围内 | `{"minCount": 2, "maxCount": 5}` |
|
||||
| 机位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
|
||||
@@ -337,9 +462,66 @@ POST /puzzle/generate
|
||||
|
||||
---
|
||||
|
||||
### 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接收
|
||||
@@ -350,6 +532,16 @@ POST /puzzle/generate
|
||||
↓
|
||||
根据templateId查询所有元素(按z-index排序)
|
||||
↓
|
||||
【新增】调用PuzzleElementFillEngine.execute()(自动填充)
|
||||
├─ 查询该模板的所有填充规则(按优先级排序)
|
||||
├─ 构建ConditionContext(机位数量、机位列表等)
|
||||
├─ 遍历规则进行条件匹配
|
||||
├─ 找到匹配规则后,加载其明细列表
|
||||
├─ 对每条明细调用DataSourceResolver解析数据源
|
||||
└─ 返回Map<elementKey, dataValue>
|
||||
↓
|
||||
合并自动填充数据和用户手动数据(用户数据优先级更高)
|
||||
↓
|
||||
调用PuzzleImageRenderer.render()
|
||||
├─ 创建画布
|
||||
├─ 绘制背景
|
||||
@@ -595,11 +787,16 @@ System.out.println("生成成功,图片URL: " + response.getImageUrl());
|
||||
|
||||
## 🔗 相关文档
|
||||
|
||||
### 官方文档
|
||||
- [MyBatis-Plus官方文档](https://baomidou.com/)
|
||||
- [Hutool工具类文档](https://hutool.cn/)
|
||||
- [Java AWT图形绘制教程](https://docs.oracle.com/javase/tutorial/2d/)
|
||||
- [阿里云OSS Java SDK](https://help.aliyun.com/document_detail/32008.html)
|
||||
|
||||
### 项目内部文档
|
||||
- [拼图填充规则管理接口文档](../../docs/puzzle/拼图填充规则管理接口文档.md) - 前端管理页面API接口文档
|
||||
- [DeviceIdMatchConditionStrategy使用文档](../../docs/puzzle/DeviceIdMatchConditionStrategy使用文档.md) - 机位ID匹配策略详细说明
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
@@ -608,4 +805,4 @@ System.out.println("生成成功,图片URL: " + response.getImageUrl());
|
||||
|
||||
**维护者**:Claude
|
||||
**创建时间**:2025-01-17
|
||||
**最后更新**:2025-01-17
|
||||
**最后更新**:2025-01-19
|
||||
|
||||
Reference in New Issue
Block a user