package cn.iocoder.yudao.module.system.framework.agent; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import lombok.extern.slf4j.Slf4j; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.access.AccessDeniedException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.server.ResponseStatusException; import java.util.LinkedHashMap; import java.util.Map; /** * 智能体桥接专用异常映射:状态码必须真实,Python 才能区分 401/403/409/422。 * 仅作用于 agent 包,避免影响其他管理端接口的既有响应格式。 */ @Slf4j @Order(Ordered.HIGHEST_PRECEDENCE) @RestControllerAdvice(basePackages = { "cn.iocoder.yudao.module.system.controller.admin.agent", "cn.iocoder.yudao.module.mdm.controller.admin.agent" }) public class AgentBridgeExceptionHandler { @ExceptionHandler(AgentBridgeException.class) public ResponseEntity>> handleAgentBridge(AgentBridgeException ex) { return build(ex.getStatusCode().value(), ex.getReason(), ex.getErrorCode(), ex.isRetryable(), ex.getDetails()); } @ExceptionHandler(ResponseStatusException.class) public ResponseEntity>> handleResponseStatus(ResponseStatusException ex) { int status = ex.getStatusCode().value(); return build(status, ex.getReason(), defaultCode(status), false, null); } @ExceptionHandler(AccessDeniedException.class) public ResponseEntity>> handleAccessDenied(AccessDeniedException ex) { return build(HttpStatus.FORBIDDEN.value(), "当前身份缺少物料导入权限(mdm:item:import)", "FORBIDDEN", false, null); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity>> handleInvalidArgument(MethodArgumentNotValidException ex) { FieldError fieldError = ex.getBindingResult().getFieldError(); String message = fieldError == null ? "请求参数不合法" : fieldError.getField() + ":" + fieldError.getDefaultMessage(); return build(HttpStatus.BAD_REQUEST.value(), message, "BAD_REQUEST", false, null); } @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity>> handleUnreadable(HttpMessageNotReadableException ex) { return build(HttpStatus.BAD_REQUEST.value(), "请求体格式不正确", "BAD_REQUEST", false, null); } @ExceptionHandler(Exception.class) public ResponseEntity>> handleUnexpected(Exception ex) { log.error("[handleUnexpected][智能体桥接未预期异常]", ex); return build(HttpStatus.INTERNAL_SERVER_ERROR.value(), "智能体桥接处理失败", "INTERNAL_ERROR", true, null); } private ResponseEntity>> build( int status, String message, String errorCode, boolean retryable, Map details) { Map data = new LinkedHashMap<>(); data.put("error_code", errorCode); data.put("retryable", retryable); data.put("details", details == null ? Map.of() : details); CommonResult> result = CommonResult.error(status, message == null ? defaultMessage(status) : message); result.setData(data); return ResponseEntity.status(status).body(result); } private static String defaultCode(int status) { return switch (status) { case 401 -> "UNAUTHORIZED"; case 403 -> "FORBIDDEN"; case 404 -> "NOT_FOUND"; case 409 -> "CONFLICT"; case 413 -> "FILE_TOO_LARGE"; case 422 -> "VALIDATION_FAILED"; case 503 -> "SERVICE_UNAVAILABLE"; default -> status >= 500 ? "INTERNAL_ERROR" : "BAD_REQUEST"; }; } private static String defaultMessage(int status) { return switch (status) { case 401 -> "未登录或登录已过期"; case 403 -> "当前身份无权访问"; case 404 -> "资源不存在"; case 409 -> "请求冲突"; case 413 -> "文件超过大小限制"; case 422 -> "请求数据未通过校验"; case 503 -> "服务暂不可用"; default -> status >= 500 ? "服务处理失败" : "请求不合法"; }; } }