From b7bb8af200cff1eb645e655982e21a666108d7c7 Mon Sep 17 00:00:00 2001 From: lzq986 <2719916844@qq.com> Date: Mon, 3 Aug 2026 23:36:45 +0800 Subject: [PATCH] fix(exception): sanitize leaked error messages and log 4xx client errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IllegalArgumentException / MethodArgumentTypeMismatchException 不再向响应体透传原始 message(可能含连接串、内部类型名),详情进日志 - 新增 ExceptionResponseMapper.logMapped 统一 HTTP/SSE/tool 三出口日志策略:5xx 记完整堆栈,4xx 记 warn 摘要 - resolveConstraintField 保留嵌套字段路径(arg0.address.city),修复字段定位丢失 --- .../github/malonetalk/agent/AgentService.java | 4 +--- .../agent/tools/ExecuteSqlTool.java | 1 - .../agent/tools/GenerateReportTool.java | 1 - .../agent/tools/GetDomainsTool.java | 1 - .../agent/tools/GetTableSchemaTool.java | 1 - .../malonetalk/agent/tools/GetTablesTool.java | 1 - .../exception/ExceptionResponseMapper.java | 22 ++++++++++++++++--- .../exception/GlobalExceptionHandler.java | 17 +++++++------- .../exception/ToolExceptionMapper.java | 6 ++--- 9 files changed, 30 insertions(+), 24 deletions(-) diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/AgentService.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/AgentService.java index 8038dbc..8a0591d 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/AgentService.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/AgentService.java @@ -138,9 +138,7 @@ private ToolResultBlock toToolResultBlock(ChatRequest.ToolResultInput toolResult private Flux toErrorEvent(Throwable exception) { ErrorResponse errorResponse = exceptionResponseMapper.resolve(exception); - if (errorResponse.isServerError()) { - log.error("Agent stream failed", exception); - } + exceptionResponseMapper.logMapped(log, exception, errorResponse); return Flux.just( ChatStreamEvent.builder() .type(ChatStreamEventType.ERROR) diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java index 5b7c386..64d5ade 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/ExecuteSqlTool.java @@ -48,7 +48,6 @@ public ToolResultBlock executeSql( @ToolParam(name = "sql", description = "The SELECT SQL query statement to execute") String sql) { return toolExceptionMapper.run( - "execute SQL", () -> { Datasource datasource = dataSourceService diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GenerateReportTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GenerateReportTool.java index 165d187..a36f2fb 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GenerateReportTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GenerateReportTool.java @@ -88,7 +88,6 @@ public ToolResultBlock generateReport( } String reportSessionId = sessionId; return toolExceptionMapper.run( - "generate report", () -> ToolResultBlock.text( ToolCallConstants.SUCCESS_PREFIX diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetDomainsTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetDomainsTool.java index f717529..eeaea25 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetDomainsTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetDomainsTool.java @@ -39,7 +39,6 @@ public class GetDomainsTool implements MarkAgentTool { + " what domains are available before querying tables.") public ToolResultBlock getDomains() { return toolExceptionMapper.run( - "get domains", () -> ToolResultBlock.text( JsonUtils.getJsonCodec().toJson(domainService.listDomainNames()))); diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java index e0c0e7c..da6f1d8 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTableSchemaTool.java @@ -55,7 +55,6 @@ public ToolResultBlock getTableSchema( @ToolParam(name = "table_name", description = "The table name to query schema for") String tableName) { return toolExceptionMapper.run( - "get table schema", () -> { Datasource datasource = dataSourceService diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java index 76dd7ce..7c23094 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/agent/tools/GetTablesTool.java @@ -60,7 +60,6 @@ public ToolResultBlock getTables( required = false) List domains) { return toolExceptionMapper.run( - "get tables", () -> { Datasource dataSource = dataSourceService diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/exception/ExceptionResponseMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/exception/ExceptionResponseMapper.java index 093cbb9..77a6686 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/exception/ExceptionResponseMapper.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/exception/ExceptionResponseMapper.java @@ -18,6 +18,8 @@ package io.github.malonetalk.exception; import io.github.malonetalk.common.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.TransientDataAccessException; @@ -29,6 +31,7 @@ /** 将已知异常统一映射为 ErrorCode 和对外提示,供 HTTP、SSE、agent tool 共用。 */ @Component +@Slf4j public class ExceptionResponseMapper { /** 统一异常映射入口;沿 cause 链逐层识别,被框架包装过的异常仍能保留原 ErrorCode。 */ @@ -48,9 +51,10 @@ private ErrorResponse map(Throwable current) { return fromBusinessException(businessException); } if (current instanceof IllegalArgumentException) { - // 未迁移的裸参数断言统一视为请求参数错误, - // 避免这些调用点落成 500。 - return of(ErrorCode.BAD_REQUEST, current.getMessage()); + // 未迁移的裸参数断言统一视为请求参数错误,避免落成 500; + // 原始 message 可能含连接串等内部细节,只进日志不进响应体。 + log.debug("IllegalArgumentException mapped to BAD_REQUEST: {}", current.getMessage()); + return of(ErrorCode.BAD_REQUEST); } if (current instanceof DataIntegrityViolationException) { return of(ErrorCode.DATA_CONFLICT); @@ -91,6 +95,18 @@ private ErrorResponse fromBusinessException(BusinessException exception) { return of(exception.getErrorCode(), exception.getMessage()); } + /** 统一日志策略:5xx 记完整堆栈,4xx 记一行摘要;HTTP、SSE、tool 三出口共用。 */ + public void logMapped(Logger logger, Throwable exception, ErrorResponse errorResponse) { + if (errorResponse.isServerError()) { + logger.error("Mapped server exception", exception); + } else { + logger.warn( + "Mapped client error: {} - {}", + errorResponse.errorCode(), + errorResponse.message()); + } + } + /** 对外错误文案不能是空值,避免前端拿到不可展示的 message。 */ private String defaultIfBlank(String value, String defaultValue) { return value == null || value.isBlank() ? defaultValue : value; diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/exception/GlobalExceptionHandler.java b/data-agent-backend/src/main/java/io/github/malonetalk/exception/GlobalExceptionHandler.java index 81641b5..4be9ed8 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/exception/GlobalExceptionHandler.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/exception/GlobalExceptionHandler.java @@ -87,7 +87,7 @@ public ResponseEntity> handleConstraintViolation( @ExceptionHandler(Exception.class) public ResponseEntity> handleException(Exception exception) { ErrorResponse errorResponse = exceptionResponseMapper.resolve(exception); - logMappedException(exception, errorResponse); + exceptionResponseMapper.logMapped(log, exception, errorResponse); return response(errorResponse); } @@ -95,6 +95,10 @@ private String resolveBadRequestMessage(Exception exception) { if (exception instanceof HttpMessageNotReadableException) { return "Malformed request body."; } + if (exception instanceof MethodArgumentTypeMismatchException typeMismatch) { + // 转换异常消息含内部类型名(如 java.lang.Long),对外只暴露参数名。 + return "Invalid value for parameter '" + typeMismatch.getName() + "'."; + } return exception.getMessage() == null ? "Invalid request parameters." : exception.getMessage(); @@ -129,21 +133,16 @@ private FieldValidationError toFieldValidationError(ObjectError error) { return new FieldValidationError(field, message); } - /** ConstraintViolation 的路径可能带方法名或对象名前缀,这里只保留最后一级字段。 */ + /** ConstraintViolation 的路径形如 "tableName"(参数直接约束)或 "arg0.address.city"(嵌套 DTO), + * 去掉第一段参数/方法前缀,保留嵌套字段路径供前端定位。 */ private String resolveConstraintField(String propertyPath) { - int separatorIndex = propertyPath.lastIndexOf('.'); + int separatorIndex = propertyPath.indexOf('.'); if (separatorIndex < 0 || separatorIndex == propertyPath.length() - 1) { return propertyPath; } return propertyPath.substring(separatorIndex + 1); } - private void logMappedException(Exception exception, ErrorResponse errorResponse) { - if (errorResponse.isServerError()) { - log.error("Mapped server exception", exception); - } - } - /** 按错误码和指定文案组装 HTTP 响应。 */ private ResponseEntity> response(ErrorCode errorCode, String message) { return response(exceptionResponseMapper.of(errorCode, message)); diff --git a/data-agent-backend/src/main/java/io/github/malonetalk/exception/ToolExceptionMapper.java b/data-agent-backend/src/main/java/io/github/malonetalk/exception/ToolExceptionMapper.java index 992ab59..b0450ca 100644 --- a/data-agent-backend/src/main/java/io/github/malonetalk/exception/ToolExceptionMapper.java +++ b/data-agent-backend/src/main/java/io/github/malonetalk/exception/ToolExceptionMapper.java @@ -36,7 +36,7 @@ public class ToolExceptionMapper { private final ExceptionResponseMapper exceptionResponseMapper; - public ToolResultBlock run(String actionName, ToolAction action) { + public ToolResultBlock run(ToolAction action) { try { return action.run(); } catch (ToolSuspendException exception) { @@ -44,9 +44,7 @@ public ToolResultBlock run(String actionName, ToolAction action) { throw exception; } catch (Exception exception) { ErrorResponse errorResponse = exceptionResponseMapper.resolve(exception); - if (errorResponse.isServerError()) { - log.error("Tool action failed: {}", actionName, exception); - } + exceptionResponseMapper.logMapped(log, exception, errorResponse); return toToolError(errorResponse); } }