From 6b36f0ee52bcdef0a68fbec41bd5eb3e4db41c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=92?= Date: Mon, 3 Nov 2025 11:54:11 +0800 Subject: [PATCH] 1 --- doc/图片转换功能说明.md | 170 +++++++++ pom.xml | 12 + sql/image_conversions.sql | 18 + .../business/controller/ImageController.java | 88 +++++ .../controller/jd/JDInnerController.java | 10 +- .../business/model/pl/ImageConversion.java | 67 ++++ .../repository/ImageConversionRepository.java | 33 ++ .../business/service/ImageConvertService.java | 334 ++++++++++++++++++ src/main/resources/application.yml | 11 + 9 files changed, 741 insertions(+), 2 deletions(-) create mode 100644 doc/图片转换功能说明.md create mode 100644 sql/image_conversions.sql create mode 100644 src/main/java/cn/van/business/controller/ImageController.java create mode 100644 src/main/java/cn/van/business/model/pl/ImageConversion.java create mode 100644 src/main/java/cn/van/business/repository/ImageConversionRepository.java create mode 100644 src/main/java/cn/van/business/service/ImageConvertService.java diff --git a/doc/图片转换功能说明.md b/doc/图片转换功能说明.md new file mode 100644 index 0000000..6c630ea --- /dev/null +++ b/doc/图片转换功能说明.md @@ -0,0 +1,170 @@ +# 评论图片WebP转JPG功能说明 + +## 功能概述 + +评论模块中的图片如果是webp格式,会自动转换为jpg格式。转换后的图片会被缓存,下次不需要再次转换。 + +## 功能特性 + +1. **自动检测**:自动检测图片URL中的webp格式 +2. **格式转换**:将webp格式图片转换为jpg格式 +3. **结果缓存**:转换结果存储在数据库中,避免重复转换 +4. **文件存储**:转换后的jpg图片保存到本地目录 +5. **HTTP访问**:通过ImageController提供HTTP访问接口 + +## 配置说明 + +在 `application.yml` 中配置: + +```yaml +image: + convert: + # 图片存储路径(转换后的jpg图片存储目录) + storage-path: ${user.home}/comment-images + # 图片访问基础URL(如果配置,转换后的图片将通过此URL访问) + # 例如: http://your-domain.com/images 或 http://localhost:6666/images + # 如果为空,则返回本地文件路径 + base-url: http://localhost:6666/images +``` + +### 配置项说明 + +- **storage-path**:转换后的jpg图片存储目录,默认使用 `${user.home}/comment-images` +- **base-url**:图片访问的基础URL,如果配置,转换后的图片URL将使用此地址;如果不配置,则返回本地文件路径 + +## 数据库表 + +需要执行以下SQL创建图片转换记录表: + +```sql +-- 执行 sql/image_conversions.sql +``` + +表结构: +- `id`:主键ID +- `original_url`:原始webp图片URL(唯一索引) +- `converted_url`:转换后的jpg图片URL或路径 +- `converted_at`:转换时间 +- `file_size`:文件大小(字节) +- `created_at`:创建时间 + +## 工作流程 + +1. **图片URL解析**:从评论数据中解析出图片URL列表 +2. **格式检测**:检查URL中是否包含webp格式标识 +3. **缓存查询**:查询数据库,检查是否已转换过 +4. **格式转换**(如果未转换过): + - 下载原始webp图片 + - 使用webp-imageio库读取webp格式 + - 转换为BufferedImage + - 保存为jpg格式到本地目录 + - 保存转换记录到数据库 +5. **返回结果**:返回转换后的图片URL(或原URL,如果不是webp) + +## 依赖库 + +项目已添加以下依赖: + +1. **Thumbnailator** (0.4.20):图片处理库 +2. **webp-imageio** (0.1.0):WebP格式支持库 + +## API接口 + +### 图片访问接口 + +**GET** `/images/{filename}` + +访问转换后的jpg图片文件。 + +**参数:** +- `filename`:文件名(通常是MD5值.jpg) + +**返回:** +- 成功:图片文件(Content-Type: image/jpeg) +- 失败:404 Not Found 或 400 Bad Request + +**安全特性:** +- 防止路径遍历攻击 +- 文件路径验证 +- 仅允许访问存储目录内的文件 + +## 使用示例 + +### 前端调用 + +评论生成接口返回的图片URL列表已经过转换处理: + +```json +{ + "list": [ + { + "commentText": "评论内容", + "images": [ + "http://localhost:6666/images/abc123.jpg", + "http://localhost:6666/images/def456.jpg" + ] + } + ] +} +``` + +### 后端调用 + +图片转换服务会自动集成到评论生成流程中: + +```java +// 在 JDInnerController.commentGenerate 方法中 +List imageUrls = parsePictureUrls(commentToUse.getPictureUrls()); +List convertedImageUrls = imageConvertService.convertImageUrls(imageUrls); +``` + +## 注意事项 + +1. **首次转换**:首次转换webp图片时,需要下载图片并转换,可能耗时较长 +2. **存储空间**:转换后的jpg图片会占用磁盘空间,建议定期清理旧文件 +3. **网络依赖**:转换过程需要下载原始图片,确保网络连接正常 +4. **WebP支持**:确保webp-imageio库正确加载,否则转换会失败 +5. **并发处理**:多个请求同时转换同一张图片时,可能会导致重复转换(建议后续优化为加锁机制) + +## 故障排查 + +### 问题1:转换失败,提示"无法读取webp图片格式" + +**解决方案:** +1. 确认 `webp-imageio` 依赖已正确添加到pom.xml +2. 确认依赖已成功下载(检查Maven本地仓库) +3. 检查日志中的WebP支持检测信息 + +### 问题2:图片无法访问(404) + +**解决方案:** +1. 检查 `storage-path` 配置是否正确 +2. 确认图片文件已成功转换并保存 +3. 检查文件权限 +4. 如果配置了 `base-url`,确认URL是否正确 + +### 问题3:转换后的图片URL是本地路径 + +**解决方案:** +在 `application.yml` 中配置 `base-url`: + +```yaml +image: + convert: + base-url: http://your-domain:6666/images +``` + +## 性能优化建议 + +1. **异步转换**:对于大量图片,可以考虑异步转换 +2. **CDN加速**:将转换后的图片上传到CDN,提供更快的访问速度 +3. **定期清理**:定期清理长时间未使用的转换图片 +4. **缓存预热**:提前转换常用的图片 + +## 后续优化 + +1. 添加转换任务队列,支持批量转换 +2. 添加转换状态监控和统计 +3. 支持其他图片格式转换(如png、gif等) +4. 添加图片压缩功能,减小文件大小 + diff --git a/pom.xml b/pom.xml index 0f40bae..8e2d544 100644 --- a/pom.xml +++ b/pom.xml @@ -113,6 +113,18 @@ httpclient 4.5.13 + + + net.coobird + thumbnailator + 0.4.20 + + + + com.github.gotson + webp-imageio + 0.1.0 + diff --git a/sql/image_conversions.sql b/sql/image_conversions.sql new file mode 100644 index 0000000..3107c37 --- /dev/null +++ b/sql/image_conversions.sql @@ -0,0 +1,18 @@ +-- 图片转换记录表 +-- 用于存储webp格式图片转换为jpg格式的映射关系 + +CREATE TABLE IF NOT EXISTS `image_conversions` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `original_url` VARCHAR(2048) NOT NULL COMMENT '原始webp图片URL', + `converted_url` VARCHAR(2048) NOT NULL COMMENT '转换后的jpg图片URL或本地路径', + `converted_at` DATETIME NOT NULL COMMENT '转换时间', + `file_size` BIGINT(20) DEFAULT NULL COMMENT '文件大小(字节)', + `created_at` DATETIME NOT NULL COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_original_url` (`original_url`(255)), + KEY `idx_original_url` (`original_url`(255)), + KEY `idx_converted_url` (`converted_url`(255)), + KEY `idx_converted_at` (`converted_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图片转换记录表'; + + diff --git a/src/main/java/cn/van/business/controller/ImageController.java b/src/main/java/cn/van/business/controller/ImageController.java new file mode 100644 index 0000000..00fc0b2 --- /dev/null +++ b/src/main/java/cn/van/business/controller/ImageController.java @@ -0,0 +1,88 @@ +package cn.van.business.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * 图片访问控制器 + * 用于访问转换后的jpg图片 + * + * @author System + */ +@Slf4j +@RestController +@RequestMapping("/images") +public class ImageController { + + /** + * 图片存储根目录 + */ + @Value("${image.convert.storage-path:${java.io.tmpdir}/comment-images}") + private String storagePath; + + /** + * 获取转换后的图片 + * + * @param filename 文件名(通常是MD5值.jpg) + * @return 图片文件 + */ + @GetMapping("/{filename:.+}") + public ResponseEntity getImage(@PathVariable String filename) { + try { + // 安全检查:防止路径遍历攻击 + if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) { + log.warn("非法的文件名请求: {}", filename); + return ResponseEntity.badRequest().build(); + } + + Path filePath = Paths.get(storagePath, filename); + File file = filePath.toFile(); + + if (!file.exists() || !file.isFile()) { + log.warn("图片文件不存在: {}", filePath); + return ResponseEntity.notFound().build(); + } + + // 检查文件是否在存储目录内(防止路径遍历) + Path storageDir = Paths.get(storagePath).toAbsolutePath().normalize(); + Path resolvedPath = filePath.toAbsolutePath().normalize(); + if (!resolvedPath.startsWith(storageDir)) { + log.warn("非法的文件路径访问: {}", resolvedPath); + return ResponseEntity.badRequest().build(); + } + + Resource resource = new FileSystemResource(file); + + // 判断文件类型 + String contentType = Files.probeContentType(filePath); + if (contentType == null) { + // 如果无法探测,默认使用image/jpeg + contentType = MediaType.IMAGE_JPEG_VALUE; + } + + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + filename + "\"") + .body(resource); + + } catch (Exception e) { + log.error("获取图片失败: {}", filename, e); + return ResponseEntity.internalServerError().build(); + } + } +} + diff --git a/src/main/java/cn/van/business/controller/jd/JDInnerController.java b/src/main/java/cn/van/business/controller/jd/JDInnerController.java index c9225e7..b53451e 100644 --- a/src/main/java/cn/van/business/controller/jd/JDInnerController.java +++ b/src/main/java/cn/van/business/controller/jd/JDInnerController.java @@ -2,6 +2,7 @@ package cn.van.business.controller.jd; import cn.van.business.model.pl.TaobaoComment; import cn.van.business.repository.TaobaoCommentRepository; +import cn.van.business.service.ImageConvertService; import cn.van.business.util.JDProductService; import cn.van.business.util.JDScheduleJob; import cn.van.business.util.JDUtil; @@ -36,14 +37,16 @@ public class JDInnerController { private final JDScheduleJob jdScheduleJob; private final CommentRepository commentRepository; private final TaobaoCommentRepository taobaoCommentRepository; + private final ImageConvertService imageConvertService; @Autowired - public JDInnerController(JDProductService jdProductService, JDUtil jdUtil, JDScheduleJob jdScheduleJob, CommentRepository commentRepository, TaobaoCommentRepository taobaoCommentRepository) { + public JDInnerController(JDProductService jdProductService, JDUtil jdUtil, JDScheduleJob jdScheduleJob, CommentRepository commentRepository, TaobaoCommentRepository taobaoCommentRepository, ImageConvertService imageConvertService) { this.jdProductService = jdProductService; this.jdUtil = jdUtil; this.jdScheduleJob = jdScheduleJob; this.commentRepository = commentRepository; this.taobaoCommentRepository = taobaoCommentRepository; + this.imageConvertService = imageConvertService; } @PostMapping("/generatePromotionContent") @@ -192,7 +195,10 @@ public class JDInnerController { JSONObject item = new JSONObject(); item.put("commentText", commentToUse.getCommentText()); - item.put("images", parsePictureUrls(commentToUse.getPictureUrls())); + // 解析图片URL并转换webp格式为jpg + List imageUrls = parsePictureUrls(commentToUse.getPictureUrls()); + List convertedImageUrls = imageConvertService.convertImageUrls(imageUrls); + item.put("images", convertedImageUrls); JSONArray arr = new JSONArray(); arr.add(item); diff --git a/src/main/java/cn/van/business/model/pl/ImageConversion.java b/src/main/java/cn/van/business/model/pl/ImageConversion.java new file mode 100644 index 0000000..af3c7e1 --- /dev/null +++ b/src/main/java/cn/van/business/model/pl/ImageConversion.java @@ -0,0 +1,67 @@ +package cn.van.business.model.pl; + +import jakarta.persistence.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 图片转换记录实体类 + * 用于记录webp格式图片转换为jpg格式的映射关系 + * + * @author System + * @version 1.0 + */ +@Entity +@Table(name = "image_conversions", indexes = { + @Index(name = "idx_original_url", columnList = "originalUrl"), + @Index(name = "idx_converted_url", columnList = "convertedUrl") +}) +@Data +public class ImageConversion { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 原始webp图片URL + */ + @Column(name = "original_url", nullable = false, length = 2048, unique = true) + private String originalUrl; + + /** + * 转换后的jpg图片URL或本地路径 + */ + @Column(name = "converted_url", nullable = false, length = 2048) + private String convertedUrl; + + /** + * 转换时间 + */ + @Column(name = "converted_at", nullable = false) + private LocalDateTime convertedAt; + + /** + * 文件大小(字节) + */ + @Column(name = "file_size") + private Long fileSize; + + /** + * 创建时间 + */ + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @PrePersist + protected void onCreate() { + if (createdAt == null) { + createdAt = LocalDateTime.now(); + } + if (convertedAt == null) { + convertedAt = LocalDateTime.now(); + } + } +} + diff --git a/src/main/java/cn/van/business/repository/ImageConversionRepository.java b/src/main/java/cn/van/business/repository/ImageConversionRepository.java new file mode 100644 index 0000000..0d1c9d5 --- /dev/null +++ b/src/main/java/cn/van/business/repository/ImageConversionRepository.java @@ -0,0 +1,33 @@ +package cn.van.business.repository; + +import cn.van.business.model.pl.ImageConversion; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/** + * 图片转换记录Repository + * + * @author System + */ +@Repository +public interface ImageConversionRepository extends JpaRepository { + + /** + * 根据原始URL查找转换记录 + * + * @param originalUrl 原始webp图片URL + * @return 转换记录 + */ + Optional findByOriginalUrl(String originalUrl); + + /** + * 检查是否已存在转换记录 + * + * @param originalUrl 原始webp图片URL + * @return 是否存在 + */ + boolean existsByOriginalUrl(String originalUrl); +} + diff --git a/src/main/java/cn/van/business/service/ImageConvertService.java b/src/main/java/cn/van/business/service/ImageConvertService.java new file mode 100644 index 0000000..386db69 --- /dev/null +++ b/src/main/java/cn/van/business/service/ImageConvertService.java @@ -0,0 +1,334 @@ +package cn.van.business.service; + +import cn.van.business.model.pl.ImageConversion; +import cn.van.business.repository.ImageConversionRepository; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.util.*; + +/** + * WebP图片IO支持 + * 注册WebP图片格式的读写器 + */ +class WebPImageIO { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WebPImageIO.class); + private static volatile boolean initialized = false; + + /** + * 初始化WebP支持 + * webp-imageio库在类加载时会通过ServiceLoader自动注册到ImageIO + */ + static synchronized void init() { + if (initialized) { + return; + } + + // 检查是否支持webp格式 + try { + Iterator readers = ImageIO.getImageReadersByFormatName("webp"); + if (readers.hasNext()) { + log.debug("WebP图片格式支持已启用"); + } else { + log.warn("未检测到WebP图片格式支持,webp转换可能失败。请确认webp-imageio依赖已正确加载"); + } + } catch (Exception e) { + log.warn("检查WebP支持时出错: {}", e.getMessage()); + } + + initialized = true; + } +} + +/** + * 图片转换服务类 + * 负责将webp格式的图片转换为jpg格式,并缓存转换结果 + * + * @author System + */ +@Slf4j +@Service +public class ImageConvertService { + + @Autowired + private ImageConversionRepository imageConversionRepository; + + /** + * 图片存储根目录,默认使用系统临时目录 + */ + @Value("${image.convert.storage-path:${java.io.tmpdir}/comment-images}") + private String storagePath; + + /** + * 图片访问基础URL,用于生成转换后图片的访问地址 + * 如果为空,则返回本地文件路径 + */ + @Value("${image.convert.base-url:}") + private String baseUrl; + + /** + * 转换图片URL列表,将webp格式转换为jpg + * + * @param imageUrls 原始图片URL列表 + * @return 转换后的图片URL列表(webp已转换为jpg,其他格式保持不变) + */ + public List convertImageUrls(List imageUrls) { + if (imageUrls == null || imageUrls.isEmpty()) { + return Collections.emptyList(); + } + + List convertedUrls = new ArrayList<>(); + for (String imageUrl : imageUrls) { + if (StrUtil.isBlank(imageUrl)) { + continue; + } + + try { + String convertedUrl = convertImageUrl(imageUrl); + convertedUrls.add(convertedUrl); + } catch (Exception e) { + log.warn("图片转换失败,使用原URL: {}, 错误: {}", imageUrl, e.getMessage()); + // 转换失败时使用原URL + convertedUrls.add(imageUrl); + } + } + + return convertedUrls; + } + + /** + * 转换单个图片URL + * + * @param originalUrl 原始图片URL + * @return 转换后的图片URL(如果不需要转换则返回原URL) + */ + private String convertImageUrl(String originalUrl) { + if (StrUtil.isBlank(originalUrl)) { + return originalUrl; + } + + // 检查是否为webp格式 + if (!isWebpFormat(originalUrl)) { + return originalUrl; + } + + // 检查是否已转换 + Optional existing = imageConversionRepository.findByOriginalUrl(originalUrl); + if (existing.isPresent()) { + ImageConversion conversion = existing.get(); + log.debug("使用缓存的转换结果: {} -> {}", originalUrl, conversion.getConvertedUrl()); + return conversion.getConvertedUrl(); + } + + // 执行转换 + try { + String convertedUrl = performConversion(originalUrl); + log.info("图片转换成功: {} -> {}", originalUrl, convertedUrl); + return convertedUrl; + } catch (Exception e) { + log.error("图片转换失败: {}, 错误: {}", originalUrl, e.getMessage(), e); + throw e; + } + } + + /** + * 检查URL是否为webp格式 + * + * @param url 图片URL + * @return 是否为webp格式 + */ + private boolean isWebpFormat(String url) { + if (StrUtil.isBlank(url)) { + return false; + } + + // 检查URL中是否包含.webp + String lowerUrl = url.toLowerCase(); + // 检查URL参数或路径中是否包含webp + return lowerUrl.contains(".webp") || + lowerUrl.contains("format=webp") || + lowerUrl.contains("?webp") || + lowerUrl.contains("&webp"); + } + + /** + * 执行图片转换 + * + * @param originalUrl 原始webp图片URL + * @return 转换后的jpg图片URL或路径 + */ + private String performConversion(String originalUrl) throws IOException { + // 确保存储目录存在 + Path storageDir = Paths.get(storagePath); + if (!Files.exists(storageDir)) { + Files.createDirectories(storageDir); + } + + // 生成转换后的文件名(基于原URL的MD5) + String fileName = generateFileName(originalUrl); + Path outputPath = storageDir.resolve(fileName); + + // 如果转换后的文件已存在,直接返回 + if (Files.exists(outputPath)) { + String convertedUrl = generateConvertedUrl(fileName); + // 如果数据库中没有记录,保存记录 + if (!imageConversionRepository.existsByOriginalUrl(originalUrl)) { + saveConversionRecord(originalUrl, convertedUrl, Files.size(outputPath)); + } + return convertedUrl; + } + + // 下载原始图片 + byte[] imageData = downloadImage(originalUrl); + if (imageData == null || imageData.length == 0) { + throw new IOException("下载图片失败或图片数据为空: " + originalUrl); + } + + // 转换图片格式 + BufferedImage bufferedImage = null; + try { + // 初始化WebP支持 + WebPImageIO.init(); + + // 尝试使用ImageIO读取 + BufferedImage tempImage = null; + try (ByteArrayInputStream bais = new ByteArrayInputStream(imageData)) { + tempImage = ImageIO.read(bais); + } + + if (tempImage != null) { + bufferedImage = tempImage; + } else { + // 如果ImageIO无法读取,尝试使用WebP特定的读取器 + Iterator readers = ImageIO.getImageReadersByFormatName("webp"); + if (readers.hasNext()) { + ImageReader reader = readers.next(); + try (ByteArrayInputStream bais = new ByteArrayInputStream(imageData); + ImageInputStream iis = ImageIO.createImageInputStream(bais)) { + reader.setInput(iis); + bufferedImage = reader.read(0); + } finally { + reader.dispose(); + } + } + + if (bufferedImage == null) { + throw new IOException("无法读取webp图片格式,请确认已添加webp-imageio依赖"); + } + } + + // 如果是RGBA格式,转换为RGB + if (bufferedImage.getType() == BufferedImage.TYPE_4BYTE_ABGR || + bufferedImage.getType() == BufferedImage.TYPE_INT_ARGB) { + BufferedImage rgbImage = new BufferedImage( + bufferedImage.getWidth(), + bufferedImage.getHeight(), + BufferedImage.TYPE_INT_RGB + ); + rgbImage.createGraphics().drawImage(bufferedImage, 0, 0, null); + bufferedImage = rgbImage; + } + + // 保存为jpg格式 + boolean success = ImageIO.write(bufferedImage, "jpg", outputPath.toFile()); + if (!success) { + throw new IOException("无法写入jpg格式"); + } + + String convertedUrl = generateConvertedUrl(fileName); + long fileSize = Files.size(outputPath); + + // 保存转换记录 + saveConversionRecord(originalUrl, convertedUrl, fileSize); + + log.info("图片转换完成: {} -> {} (大小: {} bytes)", originalUrl, convertedUrl, fileSize); + return convertedUrl; + + } finally { + if (bufferedImage != null) { + bufferedImage.flush(); + } + } + } + + /** + * 下载图片 + * + * @param url 图片URL + * @return 图片字节数组 + */ + private byte[] downloadImage(String url) { + try { + return HttpUtil.downloadBytes(url); + } catch (Exception e) { + log.error("下载图片失败: {}, 错误: {}", url, e.getMessage()); + throw new RuntimeException("下载图片失败: " + e.getMessage(), e); + } + } + + /** + * 生成文件名(基于原URL的MD5) + * + * @param originalUrl 原始URL + * @return 文件名(不含扩展名) + */ + private String generateFileName(String originalUrl) { + // 使用URL的MD5作为文件名,避免重复和特殊字符问题 + String md5 = cn.hutool.core.util.HashUtil.md5Hex(originalUrl); + return md5 + ".jpg"; + } + + /** + * 生成转换后的URL + * + * @param fileName 文件名 + * @return 转换后的URL或本地路径 + */ + private String generateConvertedUrl(String fileName) { + if (StrUtil.isNotBlank(baseUrl)) { + // 如果配置了baseUrl,返回HTTP访问地址 + return baseUrl.endsWith("/") ? baseUrl + fileName : baseUrl + "/" + fileName; + } else { + // 否则返回本地文件路径 + return Paths.get(storagePath, fileName).toString(); + } + } + + /** + * 保存转换记录 + * + * @param originalUrl 原始URL + * @param convertedUrl 转换后的URL + * @param fileSize 文件大小 + */ + private void saveConversionRecord(String originalUrl, String convertedUrl, long fileSize) { + ImageConversion conversion = new ImageConversion(); + conversion.setOriginalUrl(originalUrl); + conversion.setConvertedUrl(convertedUrl); + conversion.setFileSize(fileSize); + conversion.setConvertedAt(LocalDateTime.now()); + + try { + imageConversionRepository.save(conversion); + } catch (Exception e) { + log.warn("保存转换记录失败: {}, 错误: {}", originalUrl, e.getMessage()); + // 不抛出异常,因为转换本身已成功 + } + } +} + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index aaa0627..fb952b5 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -88,3 +88,14 @@ resilience4j.ratelimiter: timeoutDuration: 0 # 立即失败模式 registerHealthIndicator: true +# 图片转换配置 +image: + convert: + # 图片存储路径(转换后的jpg图片存储目录) + storage-path: ${user.home}/comment-images + # 图片访问基础URL(如果配置,转换后的图片将通过此URL访问) + # 例如: http://your-domain.com/images 或 http://localhost:6666/images + # 如果为空,则返回本地文件路径 + # 建议配置为:http://your-domain:6666/images (使用ImageController提供HTTP访问) + base-url: +