Skip to content

Commit 858a326

Browse files
authored
Merge pull request #359 from jamebal/develop
perf: 优化 GIF 图片的条件性缩放
2 parents 0cd093f + 30884d4 commit 858a326

File tree

4 files changed

+117
-45
lines changed

4 files changed

+117
-45
lines changed

src/main/java/com/jmal/clouddisk/dao/impl/jpa/repository/GroupRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public interface GroupRepository extends JpaRepository<GroupDO, String>, JpaSpec
3333
nativeQuery = true)
3434
List<GroupDO> findAllByRoleIdList_MySQL(@Param("roleIdListAsJson") String roleIdListAsJson);
3535

36-
@Query(value = "SELECT DISTINCT * FROM user_groups g, json_each(g.roles) je WHERE je.value IN (:roleIdList)",
36+
@Query(value = "SELECT DISTINCT g.* FROM user_groups g, json_each(g.roles) je WHERE je.value IN (:roleIdList)",
3737
nativeQuery = true)
3838
List<GroupDO> findAllByRoleIdList_SQLite(@Param("roleIdList") Collection<String> roleIdList);
3939
}

src/main/java/com/jmal/clouddisk/interceptor/FileInterceptor.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,11 @@ public class FileInterceptor implements HandlerInterceptor {
8989

9090
@Override
9191
public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws IOException {
92-
if (internalValid(request, response)) return true;
93-
94-
if (fileAuthError(request, response)) {
95-
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
96-
return false;
92+
if (!internalValid(request, response)) {
93+
if (fileAuthError(request, response)) {
94+
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
95+
return false;
96+
}
9797
}
9898
Path path = Paths.get(request.getRequestURI());
9999
String filename = getDownloadFilename(request, path);
@@ -177,18 +177,18 @@ public static void main(String[] args) {
177177
System.out.println(URLUtil.encode("未命名文件 副本.txt", StandardCharsets.UTF_8));
178178
}
179179

180+
/**
181+
* 内部请求校验
182+
*/
180183
private boolean internalValid(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) {
181184
String requestId = (String) request.getAttribute("requestId");
182185
String internalToken = (String) request.getAttribute("internalToken");
183186

184187
if (CharSequenceUtil.isNotBlank(requestId) && CharSequenceUtil.isNotBlank(internalToken)) {
185188
if (!PreFileInterceptor.isValidInternalTokenCache(requestId, internalToken)) {
186189
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
190+
return false;
187191
}
188-
Path path = Paths.get(request.getRequestURI());
189-
String filename = getDownloadFilename(request, path);
190-
if (downloadOssFile(request, response, filename, path)) return false;
191-
setHeader(request, response);
192192
return true;
193193
}
194194
return false;
@@ -366,7 +366,9 @@ private void handleCrop(HttpServletRequest request, HttpServletResponse response
366366
String w = request.getParameter("w");
367367
String h = request.getParameter("h");
368368
responseImageFileHeader(response, file.getName());
369-
ImageMagickProcessor.cropImage(new FileInputStream(file), q, w, h, response.getOutputStream());
369+
try (InputStream inputStream = new FileInputStream(file)) {
370+
ImageMagickProcessor.cropImage(inputStream, q, w, h, response.getOutputStream());
371+
}
370372
}
371373

372374
private File getFileByRequest(HttpServletRequest request) {

src/main/java/com/jmal/clouddisk/media/ImageMagickProcessor.java

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,15 @@ private String generateTempImagePath(String username) {
6767
* @param file File
6868
*/
6969
public void generateThumbnail(File file, FileDocument fileDocument) {
70-
try {
71-
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
72-
cropImage(new FileInputStream(file), "1", "256", "256", byteArrayOutputStream);
70+
try(FileInputStream fileInputStream = new FileInputStream(file);
71+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
72+
String extName = FileUtil.extName(file);
73+
if ("gif".equalsIgnoreCase(extName)) {
74+
// GIF 图片单独处理,保持动画效果
75+
cropImage(fileInputStream, "1", "256", "256", byteArrayOutputStream, "gif");
76+
} else {
77+
cropImage(fileInputStream, "1", "256", "256", byteArrayOutputStream, "png");
78+
}
7379
if (dataSourceProperties.getType() == DataSourceType.mongodb) {
7480
fileDocument.setContent(byteArrayOutputStream.toByteArray());
7581
} else {
@@ -155,6 +161,21 @@ public static void replaceWebp(File srcFile, File destFile, boolean deleteSrc) {
155161
* @param outputStream 输出流,用于接收处理后的 png 图片数据
156162
*/
157163
public static void cropImage(InputStream inputStream, String q, String w, String h, OutputStream outputStream) {
164+
cropImage(inputStream, q, w, h, outputStream, null);
165+
}
166+
167+
/**
168+
* 使用单一的 ImageMagick 命令,从 InputStream 对图片进行条件性缩放和质量调整。
169+
* 只有当图片尺寸大于目标尺寸时,才会进行缩放。
170+
*
171+
* @param inputStream 源图片输入流
172+
* @param q 质量 (字符串 "0.0" 到 "1.0", 默认 0.8)
173+
* @param w 目标最大宽度 (字符串)
174+
* @param h 目标最大高度 (字符串, 可选)
175+
* @param outputStream 输出流,用于接收处理后的 png 图片数据
176+
* @param outputFormat 输出格式 (png 和 gif)
177+
*/
178+
public static void cropImage(InputStream inputStream, String q, String w, String h, OutputStream outputStream, String outputFormat) {
158179
// 1. 解析输入参数
159180
double quality = parseQuality(q);
160181
int targetWidth = parseDimension(w);
@@ -167,9 +188,17 @@ public static void cropImage(InputStream inputStream, String q, String w, String
167188
// IoUtil.copy(inputStream, outputStream);
168189
return;
169190
}
191+
if (outputFormat == null) {
192+
outputFormat = "png";
193+
}
170194

171195
// 2. 构建单一的、条件性的 ImageMagick 命令
172-
CommandLine cmdLine = buildConditionalResizeCommand(targetWidth, targetHeight, quality);
196+
CommandLine cmdLine;
197+
if ("gif".equals(outputFormat)) {
198+
cmdLine = buildConditionalResizeGIFCommand(targetWidth, targetHeight);
199+
} else {
200+
cmdLine = buildConditionalResizeCommand(targetWidth, targetHeight, quality);
201+
}
173202

174203
// 3. 执行命令,将输入流管道连接到命令的标准输入
175204
try {
@@ -217,6 +246,37 @@ private static CommandLine buildConditionalResizeCommand(int targetWidth, int ta
217246
return cmdLine;
218247
}
219248

249+
/**
250+
* 构建一个使用条件缩放的 ImageMagick 命令行,专门用于处理 GIF 图片以保持动画效果。
251+
* @param targetWidth 目标宽度
252+
* @param targetHeight 目标高度 (如果<0则忽略)
253+
* @return 构建好的命令行对象
254+
*/
255+
private static CommandLine buildConditionalResizeGIFCommand(int targetWidth, int targetHeight) {
256+
CommandLine cmdLine = new CommandLine("magick");
257+
258+
cmdLine.addArgument("-", false);
259+
260+
cmdLine.addArgument("-coalesce", false);
261+
cmdLine.addArgument("-resize", false);
262+
263+
StringBuilder geometry = new StringBuilder();
264+
geometry.append(targetWidth);
265+
if (targetHeight > 0) {
266+
geometry.append("x").append(targetHeight);
267+
}
268+
geometry.append(">");
269+
270+
cmdLine.addArgument(geometry.toString(), false);
271+
272+
cmdLine.addArgument("-layers", false);
273+
cmdLine.addArgument("optimize", false);
274+
275+
cmdLine.addArgument("gif:-", false);
276+
277+
return cmdLine;
278+
}
279+
220280
/**
221281
* <p>获取图片尺寸</p>
222282
* 命令: identify -format "%w %h" image.jpg
@@ -225,18 +285,25 @@ private static CommandLine buildConditionalResizeCommand(int targetWidth, int ta
225285
* @return 一个包含 [width, height] 的数组,如果失败则返回 null
226286
*/
227287
public static ImageFormat identifyFormat(File imageFile) {
228-
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
229-
230-
CommandLine cmdLine = new CommandLine("magick");
231-
cmdLine.addArgument("identify", false);
232-
cmdLine.addArgument("-format", false);
233-
cmdLine.addArgument(ImageFormat.DEFAULT_FORMAT_PARAM, false);
234-
cmdLine.addArgument(imageFile.getAbsolutePath(), false);
235-
CommandUtil.execCommand(cmdLine, null, outputStream);
236-
237-
String output = IoUtil.toStr(outputStream, StandardCharsets.UTF_8);
288+
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
289+
CommandLine cmdLine = new CommandLine("magick");
290+
cmdLine.addArgument("identify", false);
291+
cmdLine.addArgument("-format", false);
292+
cmdLine.addArgument(ImageFormat.DEFAULT_FORMAT_PARAM, false);
293+
cmdLine.addArgument(imageFile.getAbsolutePath(), false);
294+
CommandUtil.execCommand(cmdLine, null, outputStream);
295+
296+
String output = IoUtil.toStr(outputStream, StandardCharsets.UTF_8);
297+
if (CharSequenceUtil.isBlank(output)) {
298+
log.error("ImageMagick identify command returned empty output for file: {}", imageFile.getAbsolutePath());
299+
throw new CommonException(ExceptionType.SYSTEM_ERROR.getCode(), "获取图片信息失败: identify 命令返回为空");
300+
}
301+
return ImageFormat.getImageFormat(output);
302+
} catch (IOException e) {
303+
log.error("Failed to identify image format: {}", imageFile.getAbsolutePath(), e);
304+
throw new CommonException(ExceptionType.SYSTEM_ERROR.getCode(), "获取图片信息失败");
305+
}
238306

239-
return ImageFormat.getImageFormat(output);
240307
}
241308

242309
@Builder

src/main/java/com/jmal/clouddisk/util/CommandUtil.java

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,31 @@ public static void execCommand(CommandLine cmdLine, InputStream inputStream, Out
3535
*/
3636
public static void execCommand(CommandLine cmdLine, InputStream inputStream, OutputStream outputStream, Duration timeout) {
3737
DefaultExecutor executor = DefaultExecutor.builder().get();
38-
ByteArrayOutputStream stdErr = new ByteArrayOutputStream();
39-
// 参数:标准输出、标准错误、标准输入
40-
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, stdErr, inputStream);
41-
executor.setStreamHandler(streamHandler);
42-
43-
ExecuteWatchdog watchdog = ExecuteWatchdog.builder().setTimeout(timeout).get();
44-
executor.setWatchdog(watchdog);
45-
46-
try {
47-
int exitValue = executor.execute(cmdLine);
48-
log.debug("command exec success, command: {}", String.join(" ", cmdLine.toStrings()));
49-
if (exitValue != 0) {
38+
try(ByteArrayOutputStream stdErr = new ByteArrayOutputStream()) {
39+
// 参数:标准输出、标准错误、标准输入
40+
PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, stdErr, inputStream);
41+
executor.setStreamHandler(streamHandler);
42+
43+
ExecuteWatchdog watchdog = ExecuteWatchdog.builder().setTimeout(timeout).get();
44+
executor.setWatchdog(watchdog);
45+
46+
try {
47+
int exitValue = executor.execute(cmdLine);
48+
log.debug("command exec success, command: {}", String.join(" ", cmdLine.toStrings()));
49+
if (exitValue != 0) {
50+
String errMsg = stdErr.toString(StandardCharsets.UTF_8);
51+
log.error("command exec failed, command: {}, exit code: {}, stderr: {}", String.join(" ", cmdLine.toStrings()), exitValue, errMsg);
52+
}
53+
} catch (IOException e) {
5054
String errMsg = stdErr.toString(StandardCharsets.UTF_8);
51-
log.error("command exec failed, command: {}, exit code: {}, stderr: {}", String.join(" ", cmdLine.toStrings()), exitValue, errMsg);
55+
if (watchdog.killedProcess()) {
56+
log.error("command exec timeout, command: {}, stderr: {}", String.join(" ", cmdLine.toStrings()), errMsg);
57+
} else {
58+
log.error("command exec error, command: {}, stderr: {}, {}", String.join(" ", cmdLine.toStrings()), errMsg, e.getMessage());
59+
}
5260
}
5361
} catch (IOException e) {
54-
String errMsg = stdErr.toString(StandardCharsets.UTF_8);
55-
if (watchdog.killedProcess()) {
56-
log.error("command exec timeout, command: {}, stderr: {}", String.join(" ", cmdLine.toStrings()), errMsg);
57-
} else {
58-
log.error("command exec error, command: {}, stderr: {}, {}", String.join(" ", cmdLine.toStrings()), errMsg, e.getMessage());
59-
}
62+
log.error("command exec error, command: {}, {}", String.join(" ", cmdLine.toStrings()), e.getMessage());
6063
}
6164
}
6265

0 commit comments

Comments
 (0)