package com.sinata.appointment.service; import com.sinata.appointment.config.LocalFileUploadProperties; import com.sinata.appointment.constants.BizErrorCode; import com.sinata.appointment.exception.BizException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Set; import java.util.UUID; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; @Service @Slf4j public class LocalImageUploadService { private static final long IMAGE_MAX_SIZE = 10L * 1024 * 1024; private static final Set IMAGE_EXTENSIONS = Set.of(".jpg", ".jpeg", ".png", ".webp", ".gif"); private final LocalFileUploadProperties localFileUploadProperties; public LocalImageUploadService(LocalFileUploadProperties localFileUploadProperties) { this.localFileUploadProperties = localFileUploadProperties; } public String uploadImage(MultipartFile file) { if (file == null || file.isEmpty()) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "图片文件不能为空"); } if (file.getSize() > IMAGE_MAX_SIZE) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "图片文件不能超过10MB"); } String ext = normalizeImageExtension(file.getOriginalFilename(), file.getContentType()); String fileName = UUID.randomUUID().toString().replace("-", "") + ext; Path uploadDir = Path.of(localFileUploadProperties.getFileUploadPath()).toAbsolutePath().normalize(); Path target = uploadDir.resolve(fileName).normalize(); if (!target.getParent().equals(uploadDir)) { throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "图片上传路径非法"); } try { Files.createDirectories(uploadDir); Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "图片上传失败"); } String url = buildPictureUrl(fileName); log.info("local image uploaded. path={} url={}", target, url); return url; } private String buildPictureUrl(String fileName) { String serverAddress = localFileUploadProperties.getPictureServerAddress(); if (!StringUtils.hasText(serverAddress)) { return fileName; } return serverAddress.endsWith("/") ? serverAddress + fileName : serverAddress + "/" + fileName; } private String normalizeImageExtension(String originalFilename, String contentType) { String ext = ""; if (StringUtils.hasText(originalFilename)) { int idx = originalFilename.lastIndexOf('.'); if (idx >= 0) { ext = originalFilename.substring(idx).toLowerCase(); } } if (!IMAGE_EXTENSIONS.contains(ext)) { ext = switch (contentType == null ? "" : contentType.toLowerCase()) { case "image/jpeg", "image/jpg" -> ".jpg"; case "image/png" -> ".png"; case "image/webp" -> ".webp"; case "image/gif" -> ".gif"; default -> ""; }; } if (!IMAGE_EXTENSIONS.contains(ext)) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "仅支持 jpg、jpeg、png、webp、gif 图片"); } return ext; } }