# 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, Map)`:主渲染方法 - 创建画布(根据模板宽高) - 绘制背景(纯色或图片背景) - 按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)`:批量添加元素 - `updateElement(Long, ElementCreateRequest)`:更新元素 - `deleteElement(Long)`:逻辑删除元素 - `getElementDetail(Long)`:查询元素详情 **业务逻辑要点**: - 删除模板时会级联删除关联的所有元素 - 支持多租户隔离(根据scenicId) - 支持按category分类查询 - 支持按status过滤启用/禁用的模板 --- ### 4. PuzzleElementFillEngine - 自动填充引擎(新功能) **职责**:基于规则引擎自动选择和填充拼图元素的数据源 **核心概念**: - 通过配置化的规则(而非硬编码)决定每个元素使用哪些素材 - 支持基于机位数量、机位ID、人脸特征等多维度条件匹配 - 支持多种数据源(机位图片、用户头像、二维码等) - 支持灵活的排序策略(最新、评分、随机等) - 支持优先级和降级策略 **核心方法**: ```java Map 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` **条件策略(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` ```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 ↓ 合并自动填充数据和用户手动数据(用户数据优先级更高) ↓ 调用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 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()); ```