zoujiajian 10 цаг өмнө
parent
commit
d2a912cf52

+ 7 - 0
docs/frontend-security-integration.md

@@ -196,3 +196,10 @@ ip 筛选目前只精确匹配 remoteIp 或 realIp,不查询 forwardedFor 链
 - 新增前端工具 src/utils/apiError.mjs,发布时需一起提交。无 SQL 变更。
 
 后续调整:按用户要求撤回此次前端错误提示增强,删除 apiError.mjs,恢复 axios 和下载工具原有 msg 展示逻辑;前面的前端提示增强说明不再适用。其他安全修复保留。
+
+## 后端管理接口错误提示分流(2026-10-21)
+- 仅应用内 `/manage` 和 `/manage/**` 使用管理提示策略,去除 servlet contextPath 后匹配;不匹配 `/manager`、`/management`,不信任请求头伪造路径。
+- 管理接口的业务/未知异常、参数校验错误在 msg 中附加错误编号,前端沿用原有 msg 展示即可。
+- 缺少参数、参数类型错误、JSON 格式错误的细化提示只用于管理路径;字段校验管理路径增加字段名。
+- 登录/角色/权限异常沿用原业务码及提示,不改变登录跳转协议。未知错误仍不返回原始异常、SQL或堆栈。
+- 其他路径保留原业务提示及错误编号 data 约定,不新增管理字段细节。无需 SQL、无需前端提示改动。

+ 34 - 10
netflix-web/src/main/java/com/cyksj/web/GlobalExceptionHandler.java

@@ -71,17 +71,17 @@ public class GlobalExceptionHandler {
             apiCode = GatewayApiCode.CMS_USER_NO_PERMISSION;
         }
 
-        if (e instanceof org.springframework.web.bind.MissingServletRequestParameterException) {
+        if (isManageRequest() && e instanceof org.springframework.web.bind.MissingServletRequestParameterException) {
             message = "缺少必填参数:" + ((org.springframework.web.bind.MissingServletRequestParameterException) e).getParameterName();
-        } else if (e instanceof org.springframework.web.method.annotation.MethodArgumentTypeMismatchException) {
+        } else if (isManageRequest() && e instanceof org.springframework.web.method.annotation.MethodArgumentTypeMismatchException) {
             message = "参数格式不正确:" + ((org.springframework.web.method.annotation.MethodArgumentTypeMismatchException) e).getName();
-        } else if (e instanceof org.springframework.http.converter.HttpMessageNotReadableException) {
+        } else if (isManageRequest() && e instanceof org.springframework.http.converter.HttpMessageNotReadableException) {
             message = "请求体格式不正确,请检查 JSON 和字段类型";
         }
         if (apiCode != null) {
             return GatewayResponse.FAIL.newBuilder().buildGatewayCode(apiCode).toResult();
         }
-        return GatewayResponse.FAIL.newBuilder().setMsg(message).toResult("requestId=" + requestId);
+        return GatewayResponse.FAIL.newBuilder().setMsg(manageMessage(message, requestId)).toResult("requestId=" + requestId);
     }
 
     @ExceptionHandler(value = ConstraintViolationException.class)
@@ -92,9 +92,8 @@ public class GlobalExceptionHandler {
         Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
         if (!violations.isEmpty()) {
             for (ConstraintViolation<?> item : violations) {
-                System.out.println(item.getPropertyPath());
                 // 遍历错误字段信息
-                errorInfoSet.add(item.getMessage());
+                errorInfoSet.add(isManageRequest() ? item.getPropertyPath() + ":" + item.getMessage() : item.getMessage());
             }
 
             StringBuilder sbf = new StringBuilder();
@@ -107,7 +106,7 @@ public class GlobalExceptionHandler {
 
         log.error("错误字段 信息: \n Value: {}", message);
 
-        return GatewayResponse.FAIL.newBuilder().buildGatewayCode(GatewayApiCode.PARAMETER_MISSING_ERROR).setMsg(message).toResult();
+        return validationFailure(message);
     }
 
     @ExceptionHandler(BindException.class)
@@ -122,7 +121,7 @@ public class GlobalExceptionHandler {
             Set<String> errorInfoSet = new TreeSet<String>();
             for (FieldError fieldError : fieldErrorList) {
                 // 遍历错误字段信息
-                errorInfoSet.add(fieldError.getField() + ":" + (fieldError.isBindingFailure() ? "格式或类型不正确" : fieldError.getDefaultMessage()));
+                errorInfoSet.add(isManageRequest() ? fieldError.getField() + ":" + (fieldError.isBindingFailure() ? "格式或类型不正确" : fieldError.getDefaultMessage()) : fieldError.getDefaultMessage());
             }
 
             StringBuilder sbf = new StringBuilder();
@@ -130,13 +129,38 @@ public class GlobalExceptionHandler {
                 sbf.append(errorInfo);
                 sbf.append(",");
             }
-            String message = sbf.substring(0, sbf.length() - 1);
+            String message = sbf.length() == 0 ? "参数校验失败" : sbf.substring(0, sbf.length() - 1);
 
             log.error("验证参数信息是否有效 错误信息: \n Value: {}", message);
 
-            return GatewayResponse.FAIL.newBuilder().buildGatewayCode(GatewayApiCode.PARAMETER_MISSING_ERROR).setMsg(message).toResult();
+            return validationFailure(message);
 
         }
         return GatewayResponse.SUCCESS.newBuilder().buildGatewayCode(GatewayApiCode.SUCCESS).setMsg("参数校验通过").toResult();
     }
+    private boolean isManageRequest() {
+        String path = request.getRequestURI();
+        String context = request.getContextPath();
+        if (context != null && !context.isEmpty() && path.startsWith(context + "/")) {
+            path = path.substring(context.length());
+        }
+        // Do not trust client-supplied forwarded path headers.
+        return "/manage".equals(path) || path.startsWith("/manage/");
+    }
+
+    private String manageMessage(String message, String requestId) {
+        return isManageRequest() ? message + "(错误编号:" + requestId + ")" : message;
+    }
+
+    private Result<String> validationFailure(String message) {
+        if (!isManageRequest()) {
+            return GatewayResponse.FAIL.newBuilder().buildGatewayCode(GatewayApiCode.PARAMETER_MISSING_ERROR)
+                    .setMsg(message).toResult();
+        }
+        String requestId = java.util.UUID.randomUUID().toString();
+        log.warn("Validation failed requestId={} path={} message={}", requestId, request.getRequestURI(), message);
+        return GatewayResponse.FAIL.newBuilder().buildGatewayCode(GatewayApiCode.PARAMETER_MISSING_ERROR)
+                .setMsg(manageMessage(message, requestId)).toResult("requestId=" + requestId);
+    }
+
 }