You've already forked FrameTour-BE
feat(puzzle): 支持拼图原图保存与自动打印功能
- 在PuzzleGenerationRecordEntity中新增originalImageUrl字段用于存储未裁切的原图URL - 在PuzzleTemplateEntity中新增autoAddPrint、canPrint和userArea字段支持打印配置 - 更新PuzzleGenerationRecordMapper.xml以支持新字段的读写操作 - 在PuzzleGenerateServiceImpl中实现原图上传、用户区域裁切以及自动添加到打印队列逻辑 - 新增cropImage方法处理图片按指定区域裁切 - 集成PrinterService实现拼图完成后自动添加到打印队列功能 - 优化生成流程日志记录,区分原图和最终图片的URL信息
This commit is contained in:
@@ -76,6 +76,12 @@ public class PuzzleGenerationRecordEntity {
|
|||||||
@TableField("result_image_url")
|
@TableField("result_image_url")
|
||||||
private String resultImageUrl;
|
private String resultImageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始图片URL(未裁切的图片,用于打印)
|
||||||
|
*/
|
||||||
|
@TableField("original_image_url")
|
||||||
|
private String originalImageUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件大小(字节)
|
* 文件大小(字节)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -97,6 +97,24 @@ public class PuzzleTemplateEntity {
|
|||||||
@TableField("scenic_id")
|
@TableField("scenic_id")
|
||||||
private Long scenicId;
|
private Long scenicId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动添加到打印队列:1-开启 0-关闭
|
||||||
|
*/
|
||||||
|
@TableField("auto_add_print")
|
||||||
|
private Integer autoAddPrint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可以打印:1-可以 0-不可以
|
||||||
|
*/
|
||||||
|
@TableField("can_print")
|
||||||
|
private Integer canPrint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户查看区域(裁切区域),格式:x,y,w,h
|
||||||
|
*/
|
||||||
|
@TableField("user_area")
|
||||||
|
private String userArea;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建时间
|
* 创建时间
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ public interface PuzzleGenerationRecordMapper {
|
|||||||
*/
|
*/
|
||||||
int updateSuccess(@Param("id") Long id,
|
int updateSuccess(@Param("id") Long id,
|
||||||
@Param("resultImageUrl") String resultImageUrl,
|
@Param("resultImageUrl") String resultImageUrl,
|
||||||
|
@Param("originalImageUrl") String originalImageUrl,
|
||||||
@Param("resultFileSize") Long resultFileSize,
|
@Param("resultFileSize") Long resultFileSize,
|
||||||
@Param("resultWidth") Integer resultWidth,
|
@Param("resultWidth") Integer resultWidth,
|
||||||
@Param("resultHeight") Integer resultHeight,
|
@Param("resultHeight") Integer resultHeight,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.ycwl.basic.puzzle.service.IPuzzleGenerateService;
|
|||||||
import com.ycwl.basic.puzzle.util.PuzzleDuplicationDetector;
|
import com.ycwl.basic.puzzle.util.PuzzleDuplicationDetector;
|
||||||
import com.ycwl.basic.puzzle.util.PuzzleImageRenderer;
|
import com.ycwl.basic.puzzle.util.PuzzleImageRenderer;
|
||||||
import com.ycwl.basic.repository.ScenicRepository;
|
import com.ycwl.basic.repository.ScenicRepository;
|
||||||
|
import com.ycwl.basic.service.printer.PrinterService;
|
||||||
import com.ycwl.basic.storage.StorageFactory;
|
import com.ycwl.basic.storage.StorageFactory;
|
||||||
import com.ycwl.basic.utils.WxMpUtil;
|
import com.ycwl.basic.utils.WxMpUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -60,6 +61,8 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
|||||||
private final ScenicRepository scenicRepository;
|
private final ScenicRepository scenicRepository;
|
||||||
@Lazy
|
@Lazy
|
||||||
private final PuzzleDuplicationDetector duplicationDetector;
|
private final PuzzleDuplicationDetector duplicationDetector;
|
||||||
|
@Lazy
|
||||||
|
private final PrinterService printerService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PuzzleGenerateResponse generate(PuzzleGenerateRequest request) {
|
public PuzzleGenerateResponse generate(PuzzleGenerateRequest request) {
|
||||||
@@ -136,30 +139,64 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
|||||||
// 9. 渲染图片
|
// 9. 渲染图片
|
||||||
BufferedImage resultImage = imageRenderer.render(template, elements, finalDynamicData);
|
BufferedImage resultImage = imageRenderer.render(template, elements, finalDynamicData);
|
||||||
|
|
||||||
// 10. 上传到OSS
|
// 10. 上传原图到OSS(未裁切)
|
||||||
String imageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality());
|
String originalImageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality());
|
||||||
log.info("图片上传成功: url={}", imageUrl);
|
log.info("原图上传成功: url={}", originalImageUrl);
|
||||||
|
|
||||||
// 11. 更新记录为成功
|
// 11. 处理用户区域裁切
|
||||||
|
String finalImageUrl = originalImageUrl; // 默认使用原图
|
||||||
|
BufferedImage finalImage = resultImage;
|
||||||
|
|
||||||
|
if (StrUtil.isNotBlank(template.getUserArea())) {
|
||||||
|
try {
|
||||||
|
BufferedImage croppedImage = cropImage(resultImage, template.getUserArea());
|
||||||
|
finalImageUrl = uploadImage(croppedImage, template.getCode() + "_cropped", request.getOutputFormat(), request.getQuality());
|
||||||
|
finalImage = croppedImage;
|
||||||
|
log.info("裁切后图片上传成功: userArea={}, url={}", template.getUserArea(), finalImageUrl);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("图片裁切失败,使用原图: userArea={}", template.getUserArea(), e);
|
||||||
|
// 裁切失败时使用原图
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12. 更新记录为成功
|
||||||
long duration = (int) (System.currentTimeMillis() - startTime);
|
long duration = (int) (System.currentTimeMillis() - startTime);
|
||||||
long fileSize = estimateFileSize(resultImage, request.getOutputFormat());
|
long fileSize = estimateFileSize(finalImage, request.getOutputFormat());
|
||||||
recordMapper.updateSuccess(
|
recordMapper.updateSuccess(
|
||||||
record.getId(),
|
record.getId(),
|
||||||
imageUrl,
|
finalImageUrl,
|
||||||
|
originalImageUrl,
|
||||||
fileSize,
|
fileSize,
|
||||||
resultImage.getWidth(),
|
finalImage.getWidth(),
|
||||||
resultImage.getHeight(),
|
finalImage.getHeight(),
|
||||||
(int) duration
|
(int) duration
|
||||||
);
|
);
|
||||||
|
|
||||||
log.info("拼图生成成功(新生成): recordId={}, imageUrl={}, duration={}ms",
|
log.info("拼图生成成功(新生成): recordId={}, originalUrl={}, finalUrl={}, duration={}ms",
|
||||||
record.getId(), imageUrl, duration);
|
record.getId(), originalImageUrl, finalImageUrl, duration);
|
||||||
|
|
||||||
|
// 13. 检查是否自动添加到打印队列
|
||||||
|
if (template.getAutoAddPrint() != null && template.getAutoAddPrint() == 1) {
|
||||||
|
try {
|
||||||
|
Integer printRecordId = printerService.addUserPhotoFromPuzzle(
|
||||||
|
request.getUserId(),
|
||||||
|
resolvedScenicId,
|
||||||
|
request.getFaceId(),
|
||||||
|
originalImageUrl, // 使用原图URL添加到打印队列
|
||||||
|
record.getId()
|
||||||
|
);
|
||||||
|
log.info("自动添加到打印队列成功: recordId={}, printRecordId={}", record.getId(), printRecordId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("自动添加到打印队列失败: recordId={}", record.getId(), e);
|
||||||
|
// 添加失败不影响拼图生成流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return PuzzleGenerateResponse.success(
|
return PuzzleGenerateResponse.success(
|
||||||
imageUrl,
|
finalImageUrl,
|
||||||
fileSize,
|
fileSize,
|
||||||
resultImage.getWidth(),
|
finalImage.getWidth(),
|
||||||
resultImage.getHeight(),
|
finalImage.getHeight(),
|
||||||
(int) duration,
|
(int) duration,
|
||||||
record.getId(),
|
record.getId(),
|
||||||
false, // isDuplicate=false
|
false, // isDuplicate=false
|
||||||
@@ -405,4 +442,43 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
|||||||
|
|
||||||
return templateScenicId;
|
return templateScenicId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 裁切图片
|
||||||
|
* @param image 原图
|
||||||
|
* @param userArea 裁切区域,格式:x,y,w,h
|
||||||
|
* @return 裁切后的图片
|
||||||
|
*/
|
||||||
|
private BufferedImage cropImage(BufferedImage image, String userArea) {
|
||||||
|
if (StrUtil.isBlank(userArea)) {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String[] parts = userArea.split(",");
|
||||||
|
if (parts.length != 4) {
|
||||||
|
throw new IllegalArgumentException("userArea格式错误,应为:x,y,w,h");
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = Integer.parseInt(parts[0].trim());
|
||||||
|
int y = Integer.parseInt(parts[1].trim());
|
||||||
|
int w = Integer.parseInt(parts[2].trim());
|
||||||
|
int h = Integer.parseInt(parts[3].trim());
|
||||||
|
|
||||||
|
// 边界检查
|
||||||
|
if (x < 0 || y < 0 || w <= 0 || h <= 0) {
|
||||||
|
throw new IllegalArgumentException("裁切区域参数必须为正数");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x + w > image.getWidth() || y + h > image.getHeight()) {
|
||||||
|
throw new IllegalArgumentException("裁切区域超出图片边界");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行裁切
|
||||||
|
return image.getSubimage(x, y, w, h);
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("userArea格式错误,参数必须为数字", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<result column="generation_params" property="generationParams"/>
|
<result column="generation_params" property="generationParams"/>
|
||||||
<result column="content_hash" property="contentHash"/>
|
<result column="content_hash" property="contentHash"/>
|
||||||
<result column="result_image_url" property="resultImageUrl"/>
|
<result column="result_image_url" property="resultImageUrl"/>
|
||||||
|
<result column="original_image_url" property="originalImageUrl"/>
|
||||||
<result column="result_file_size" property="resultFileSize"/>
|
<result column="result_file_size" property="resultFileSize"/>
|
||||||
<result column="result_width" property="resultWidth"/>
|
<result column="result_width" property="resultWidth"/>
|
||||||
<result column="result_height" property="resultHeight"/>
|
<result column="result_height" property="resultHeight"/>
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
id, template_id, template_code, user_id, face_id, business_type,
|
id, template_id, template_code, user_id, face_id, business_type,
|
||||||
generation_params, content_hash,
|
generation_params, content_hash,
|
||||||
result_image_url, result_file_size, result_width, result_height,
|
result_image_url, original_image_url, result_file_size, result_width, result_height,
|
||||||
status, error_message, generation_duration, retry_count,
|
status, error_message, generation_duration, retry_count,
|
||||||
scenic_id, client_ip, user_agent, create_time, update_time
|
scenic_id, client_ip, user_agent, create_time, update_time
|
||||||
</sql>
|
</sql>
|
||||||
@@ -77,13 +78,13 @@
|
|||||||
INSERT INTO puzzle_generation_record (
|
INSERT INTO puzzle_generation_record (
|
||||||
template_id, template_code, user_id, face_id, business_type,
|
template_id, template_code, user_id, face_id, business_type,
|
||||||
generation_params, content_hash,
|
generation_params, content_hash,
|
||||||
result_image_url, result_file_size, result_width, result_height,
|
result_image_url, original_image_url, result_file_size, result_width, result_height,
|
||||||
status, error_message, generation_duration, retry_count,
|
status, error_message, generation_duration, retry_count,
|
||||||
scenic_id, client_ip, user_agent, create_time, update_time
|
scenic_id, client_ip, user_agent, create_time, update_time
|
||||||
) VALUES (
|
) VALUES (
|
||||||
#{templateId}, #{templateCode}, #{userId}, #{faceId}, #{businessType},
|
#{templateId}, #{templateCode}, #{userId}, #{faceId}, #{businessType},
|
||||||
#{generationParams}, #{contentHash},
|
#{generationParams}, #{contentHash},
|
||||||
#{resultImageUrl}, #{resultFileSize}, #{resultWidth}, #{resultHeight},
|
#{resultImageUrl}, #{originalImageUrl}, #{resultFileSize}, #{resultWidth}, #{resultHeight},
|
||||||
#{status}, #{errorMessage}, #{generationDuration}, #{retryCount},
|
#{status}, #{errorMessage}, #{generationDuration}, #{retryCount},
|
||||||
#{scenicId}, #{clientIp}, #{userAgent}, NOW(), NOW()
|
#{scenicId}, #{clientIp}, #{userAgent}, NOW(), NOW()
|
||||||
)
|
)
|
||||||
@@ -94,6 +95,7 @@
|
|||||||
UPDATE puzzle_generation_record
|
UPDATE puzzle_generation_record
|
||||||
<set>
|
<set>
|
||||||
<if test="resultImageUrl != null">result_image_url = #{resultImageUrl},</if>
|
<if test="resultImageUrl != null">result_image_url = #{resultImageUrl},</if>
|
||||||
|
<if test="originalImageUrl != null">original_image_url = #{originalImageUrl},</if>
|
||||||
<if test="resultFileSize != null">result_file_size = #{resultFileSize},</if>
|
<if test="resultFileSize != null">result_file_size = #{resultFileSize},</if>
|
||||||
<if test="resultWidth != null">result_width = #{resultWidth},</if>
|
<if test="resultWidth != null">result_width = #{resultWidth},</if>
|
||||||
<if test="resultHeight != null">result_height = #{resultHeight},</if>
|
<if test="resultHeight != null">result_height = #{resultHeight},</if>
|
||||||
@@ -111,6 +113,7 @@
|
|||||||
UPDATE puzzle_generation_record
|
UPDATE puzzle_generation_record
|
||||||
SET status = 1,
|
SET status = 1,
|
||||||
result_image_url = #{resultImageUrl},
|
result_image_url = #{resultImageUrl},
|
||||||
|
original_image_url = #{originalImageUrl},
|
||||||
result_file_size = #{resultFileSize},
|
result_file_size = #{resultFileSize},
|
||||||
result_width = #{resultWidth},
|
result_width = #{resultWidth},
|
||||||
result_height = #{resultHeight},
|
result_height = #{resultHeight},
|
||||||
|
|||||||
@@ -18,6 +18,9 @@
|
|||||||
<result column="category" property="category"/>
|
<result column="category" property="category"/>
|
||||||
<result column="status" property="status"/>
|
<result column="status" property="status"/>
|
||||||
<result column="scenic_id" property="scenicId"/>
|
<result column="scenic_id" property="scenicId"/>
|
||||||
|
<result column="auto_add_print" property="autoAddPrint"/>
|
||||||
|
<result column="can_print" property="canPrint"/>
|
||||||
|
<result column="user_area" property="userArea"/>
|
||||||
<result column="create_time" property="createTime"/>
|
<result column="create_time" property="createTime"/>
|
||||||
<result column="update_time" property="updateTime"/>
|
<result column="update_time" property="updateTime"/>
|
||||||
<result column="deleted" property="deleted"/>
|
<result column="deleted" property="deleted"/>
|
||||||
@@ -27,7 +30,8 @@
|
|||||||
<!-- 基础列 -->
|
<!-- 基础列 -->
|
||||||
<sql id="Base_Column_List">
|
<sql id="Base_Column_List">
|
||||||
id, name, code, canvas_width, canvas_height, background_type, background_color,
|
id, name, code, canvas_width, canvas_height, background_type, background_color,
|
||||||
background_image, cover_image, description, category, status, scenic_id, create_time, update_time, deleted, deleted_at
|
background_image, cover_image, description, category, status, scenic_id,
|
||||||
|
auto_add_print, can_print, user_area, create_time, update_time, deleted, deleted_at
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
<!-- 根据ID查询 -->
|
<!-- 根据ID查询 -->
|
||||||
@@ -68,10 +72,12 @@
|
|||||||
useGeneratedKeys="true" keyProperty="id">
|
useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO puzzle_template (
|
INSERT INTO puzzle_template (
|
||||||
name, code, canvas_width, canvas_height, background_type, background_color,
|
name, code, canvas_width, canvas_height, background_type, background_color,
|
||||||
background_image, cover_image, description, category, status, scenic_id, create_time, update_time, deleted
|
background_image, cover_image, description, category, status, scenic_id,
|
||||||
|
auto_add_print, can_print, user_area, create_time, update_time, deleted
|
||||||
) VALUES (
|
) VALUES (
|
||||||
#{name}, #{code}, #{canvasWidth}, #{canvasHeight}, #{backgroundType}, #{backgroundColor},
|
#{name}, #{code}, #{canvasWidth}, #{canvasHeight}, #{backgroundType}, #{backgroundColor},
|
||||||
#{backgroundImage}, #{coverImage}, #{description}, #{category}, #{status}, #{scenicId}, NOW(), NOW(), 0
|
#{backgroundImage}, #{coverImage}, #{description}, #{category}, #{status}, #{scenicId},
|
||||||
|
#{autoAddPrint}, #{canPrint}, #{userArea}, NOW(), NOW(), 0
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
@@ -91,6 +97,9 @@
|
|||||||
<if test="category != null">category = #{category},</if>
|
<if test="category != null">category = #{category},</if>
|
||||||
<if test="status != null">status = #{status},</if>
|
<if test="status != null">status = #{status},</if>
|
||||||
<if test="scenicId != null">scenic_id = #{scenicId},</if>
|
<if test="scenicId != null">scenic_id = #{scenicId},</if>
|
||||||
|
<if test="autoAddPrint != null">auto_add_print = #{autoAddPrint},</if>
|
||||||
|
<if test="canPrint != null">can_print = #{canPrint},</if>
|
||||||
|
<if test="userArea != null">user_area = #{userArea},</if>
|
||||||
update_time = NOW()
|
update_time = NOW()
|
||||||
</set>
|
</set>
|
||||||
WHERE id = #{id} AND deleted = 0
|
WHERE id = #{id} AND deleted = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user