From 239681d7190b5ba112dc1e7d2854860e04982f6a Mon Sep 17 00:00:00 2001 From: Marc Yin <3253984909@qq.com> Date: Thu, 23 Jul 2026 20:08:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E9=98=B2=E9=87=8D=E5=A4=8D=E6=91=98=E8=A6=81=E6=A0=88?= =?UTF-8?q?=E6=BA=A2=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idempotent/IdempotentSubmitAspect.java | 17 +++++- .../IdempotentSubmitAspectTest.java | 57 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 framework/src/test/java/com/nageoffer/ai/ragent/framework/idempotent/IdempotentSubmitAspectTest.java diff --git a/framework/src/main/java/com/nageoffer/ai/ragent/framework/idempotent/IdempotentSubmitAspect.java b/framework/src/main/java/com/nageoffer/ai/ragent/framework/idempotent/IdempotentSubmitAspect.java index f343fc82a..7213cee64 100644 --- a/framework/src/main/java/com/nageoffer/ai/ragent/framework/idempotent/IdempotentSubmitAspect.java +++ b/framework/src/main/java/com/nageoffer/ai/ragent/framework/idempotent/IdempotentSubmitAspect.java @@ -20,6 +20,9 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.DigestUtil; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializer; import com.nageoffer.ai.ragent.framework.context.UserContext; import com.nageoffer.ai.ragent.framework.exception.ClientException; import lombok.RequiredArgsConstructor; @@ -33,6 +36,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.multipart.MultipartFile; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; @@ -47,7 +51,18 @@ public final class IdempotentSubmitAspect { private final RedissonClient redissonClient; - private final Gson gson = new Gson(); + private final Gson gson = new GsonBuilder() + .registerTypeHierarchyAdapter( + MultipartFile.class, + (JsonSerializer) (file, type, context) -> { + JsonObject json = new JsonObject(); + json.addProperty("name", file.getName()); + json.addProperty("originalFilename", file.getOriginalFilename()); + json.addProperty("contentType", file.getContentType()); + json.addProperty("size", file.getSize()); + return json; + }) + .create(); @Value("${app.eval.enabled:false}") private boolean evalEnabled; diff --git a/framework/src/test/java/com/nageoffer/ai/ragent/framework/idempotent/IdempotentSubmitAspectTest.java b/framework/src/test/java/com/nageoffer/ai/ragent/framework/idempotent/IdempotentSubmitAspectTest.java new file mode 100644 index 000000000..07f6b0e6c --- /dev/null +++ b/framework/src/test/java/com/nageoffer/ai/ragent/framework/idempotent/IdempotentSubmitAspectTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.nageoffer.ai.ragent.framework.idempotent; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.jupiter.api.Test; +import org.redisson.api.RedissonClient; +import org.springframework.mock.web.MockMultipartFile; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class IdempotentSubmitAspectTest { + + @Test + void shouldGenerateDigestFromMultipartMetadataWithoutReadingContent() throws Exception { + byte[] firstContent = new byte[20 * 1024 * 1024]; + byte[] differentContent = firstContent.clone(); + differentContent[differentContent.length - 1] = 1; + MockMultipartFile first = new MockMultipartFile("file", "first.pdf", "application/pdf", firstContent); + MockMultipartFile sameMetadata = + new MockMultipartFile("file", "first.pdf", "application/pdf", differentContent); + MockMultipartFile differentName = + new MockMultipartFile("file", "second.pdf", "application/pdf", firstContent); + + assertEquals(calcArgsMd5(first), calcArgsMd5(sameMetadata)); + assertNotEquals(calcArgsMd5(first), calcArgsMd5(differentName)); + } + + private String calcArgsMd5(MockMultipartFile file) throws Exception { + ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class); + when(joinPoint.getArgs()).thenReturn(new Object[]{file}); + IdempotentSubmitAspect aspect = new IdempotentSubmitAspect(mock(RedissonClient.class)); + Method method = IdempotentSubmitAspect.class.getDeclaredMethod("calcArgsMD5", ProceedingJoinPoint.class); + method.setAccessible(true); + return (String) method.invoke(aspect, joinPoint); + } +}