fix(app): 修复 AppController 安全与查询问题

- 加强异常处理、类型安全和图片上传校验
- 优化设备相关查询,避免重复访问数据源
- 补充并记录并发测试与审查修复实施计划
This commit is contained in:
yuhaiming
2026-07-17 08:20:44 +08:00
parent 4a4aabf19e
commit 70e2356f22
82 changed files with 6070 additions and 583 deletions

View File

@@ -52,6 +52,12 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -12,7 +12,9 @@ import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.exception.base.BaseException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
@@ -57,9 +59,11 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(ServiceException.class)
public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
log.error(e.getMessage());
String requestURI = request.getRequestURI();
String message = resolveErrorMessage(e.getMessage());
log.warn("请求地址'{}',发生业务异常'{}'", requestURI, message, e);
Integer code = e.getCode();
return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
return ObjectUtil.isNotNull(code) ? R.fail(code, message) : R.fail(message);
}
/**
@@ -88,8 +92,10 @@ public class GlobalExceptionHandler {
*/
@ExceptionHandler(BaseException.class)
public R<Void> handleBaseException(BaseException e, HttpServletRequest request) {
log.error(e.getMessage());
return R.fail(e.getMessage());
String requestURI = request.getRequestURI();
String message = e.getMessage();
log.warn("请求地址'{}',发生业务异常'{}'", requestURI, message, e);
return R.fail(message);
}
/**
@@ -150,7 +156,7 @@ public class GlobalExceptionHandler {
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return R.fail(e.getMessage());
return R.fail();
}
/**
@@ -160,7 +166,7 @@ public class GlobalExceptionHandler {
public R<Void> handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return R.fail(e.getMessage());
return R.fail();
}
/**
@@ -232,4 +238,12 @@ public class GlobalExceptionHandler {
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "SpEL解析失败" + e.getMessage());
}
private String resolveErrorMessage(String message) {
if (StringUtils.isBlank(message)) {
return MessageUtils.message("operation.fail");
}
String i18nMessage = MessageUtils.message(message);
return message.equals(i18nMessage) ? message : i18nMessage;
}
}

View File

@@ -0,0 +1,89 @@
package org.dromara.common.web.handler;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.base.BaseException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.StaticMessageSource;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@Tag("dev")
class GlobalExceptionHandlerTest {
@Mock
private HttpServletRequest request;
private GlobalExceptionHandler handler;
private GenericApplicationContext applicationContext;
@BeforeEach
void setUp() {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage("operation.fail", Locale.getDefault(), "操作失败");
applicationContext = new GenericApplicationContext();
applicationContext.registerBean("messageSource", StaticMessageSource.class, () -> messageSource);
applicationContext.refresh();
new SpringUtil().setApplicationContext(applicationContext);
handler = new GlobalExceptionHandler();
when(request.getRequestURI()).thenReturn("/app/v1/device/D01");
}
@AfterEach
void tearDown() {
applicationContext.close();
}
@Test
void handleRuntimeException_hidesInternalMessage() {
R<Void> result = handler.handleRuntimeException(
new IllegalStateException("jdbc:mysql://internal-host/water"), request);
assertThat(result.getCode()).isEqualTo(R.FAIL);
assertThat(result.getMsg()).doesNotContain("internal-host");
}
@Test
void handleException_hidesInternalMessage() {
R<Void> result = handler.handleException(
new Exception("oss accessKeySecret=internal-secret"), request);
assertThat(result.getCode()).isEqualTo(R.FAIL);
assertThat(result.getMsg()).doesNotContain("internal-secret");
}
@Test
void handleBaseException_logsThrowableAndRequestUri() {
Logger logger = (Logger) LoggerFactory.getLogger(GlobalExceptionHandler.class);
ListAppender<ILoggingEvent> appender = new ListAppender<>();
appender.start();
logger.addAppender(appender);
try {
handler.handleBaseException(new BaseException("user", "业务校验失败"), request);
ILoggingEvent event = appender.list.get(appender.list.size() - 1);
assertThat(event.getLevel()).isEqualTo(Level.WARN);
assertThat(event.getFormattedMessage()).contains("/app/v1/device/D01");
assertThat(event.getThrowableProxy()).isNotNull();
} finally {
logger.detachAppender(appender);
}
}
}