mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-11-04 10:57:10 +08:00 
			
		
		
		
	refactor: 💥 分离 HTTP 状态码和业务状态码
1.传输正常的情况下无论业务是否有异常,HTTP 状态码始终为 200 2.防止非 HTTPS 情况下出现运营商劫持(例如:404)
This commit is contained in:
		@@ -70,7 +70,7 @@ public class GlobalErrorHandler extends BasicErrorController {
 | 
			
		||||
        R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
 | 
			
		||||
        result.setData(path);
 | 
			
		||||
        try {
 | 
			
		||||
            response.setStatus(status.value());
 | 
			
		||||
            response.setStatus(HttpStatus.OK.value());
 | 
			
		||||
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
 | 
			
		||||
            objectMapper.writeValue(response.getWriter(), result);
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
@@ -89,6 +89,6 @@ public class GlobalErrorHandler extends BasicErrorController {
 | 
			
		||||
        R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
 | 
			
		||||
        result.setData(path);
 | 
			
		||||
        log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
 | 
			
		||||
        return new ResponseEntity<>(BeanUtil.beanToMap(result), status);
 | 
			
		||||
        return new ResponseEntity<>(BeanUtil.beanToMap(result), HttpStatus.OK);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,6 @@ import org.springframework.validation.BindException;
 | 
			
		||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
 | 
			
		||||
import org.springframework.web.bind.MethodArgumentNotValidException;
 | 
			
		||||
import org.springframework.web.bind.annotation.ExceptionHandler;
 | 
			
		||||
import org.springframework.web.bind.annotation.ResponseStatus;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
 | 
			
		||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
 | 
			
		||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
 | 
			
		||||
@@ -62,7 +61,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截自定义验证异常-错误请求
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.BAD_REQUEST)
 | 
			
		||||
    @ExceptionHandler(BadRequestException.class)
 | 
			
		||||
    public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
 | 
			
		||||
        log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
 | 
			
		||||
@@ -73,7 +71,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截校验异常-违反约束异常
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.BAD_REQUEST)
 | 
			
		||||
    @ExceptionHandler(ConstraintViolationException.class)
 | 
			
		||||
    public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
 | 
			
		||||
        log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
 | 
			
		||||
@@ -85,7 +82,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截校验异常-绑定异常
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.BAD_REQUEST)
 | 
			
		||||
    @ExceptionHandler(BindException.class)
 | 
			
		||||
    public R handleBindException(BindException e, HttpServletRequest request) {
 | 
			
		||||
        log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
 | 
			
		||||
@@ -97,7 +93,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截校验异常-方法参数无效异常
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.BAD_REQUEST)
 | 
			
		||||
    @ExceptionHandler(MethodArgumentNotValidException.class)
 | 
			
		||||
    public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
 | 
			
		||||
        log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
 | 
			
		||||
@@ -110,7 +105,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截校验异常-方法参数类型不匹配异常
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.BAD_REQUEST)
 | 
			
		||||
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
 | 
			
		||||
    public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
 | 
			
		||||
        HttpServletRequest request) {
 | 
			
		||||
@@ -123,7 +117,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截文件上传异常-超过上传大小限制
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.BAD_REQUEST)
 | 
			
		||||
    @ExceptionHandler(MaxUploadSizeExceededException.class)
 | 
			
		||||
    public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
 | 
			
		||||
        log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
 | 
			
		||||
@@ -136,7 +129,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 认证异常-登录认证
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
 | 
			
		||||
    @ExceptionHandler(NotLoginException.class)
 | 
			
		||||
    public R handleNotLoginException(NotLoginException e, HttpServletRequest request) {
 | 
			
		||||
        log.error("请求地址 [{}],认证失败,无法访问系统资源。", request.getRequestURI(), e);
 | 
			
		||||
@@ -159,7 +151,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 认证异常-权限认证
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.FORBIDDEN)
 | 
			
		||||
    @ExceptionHandler(NotPermissionException.class)
 | 
			
		||||
    public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
 | 
			
		||||
        log.error("请求地址 [{}],权限码校验失败。", request.getRequestURI(), e);
 | 
			
		||||
@@ -169,7 +160,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 认证异常-角色认证
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.FORBIDDEN)
 | 
			
		||||
    @ExceptionHandler(NotRoleException.class)
 | 
			
		||||
    public R handleNotRoleException(NotRoleException e, HttpServletRequest request) {
 | 
			
		||||
        log.error("请求地址 [{}],角色权限校验失败。", request.getRequestURI(), e);
 | 
			
		||||
@@ -179,7 +169,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截校验异常-请求方式不支持异常
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
 | 
			
		||||
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
 | 
			
		||||
    public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
 | 
			
		||||
        LogContextHolder.setErrorMsg(e.getMessage());
 | 
			
		||||
@@ -190,7 +179,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截业务异常
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
 | 
			
		||||
    @ExceptionHandler(ServiceException.class)
 | 
			
		||||
    public R handleServiceException(ServiceException e, HttpServletRequest request) {
 | 
			
		||||
        log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
 | 
			
		||||
@@ -201,7 +189,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截未知的运行时异常
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
 | 
			
		||||
    @ExceptionHandler(RuntimeException.class)
 | 
			
		||||
    public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
 | 
			
		||||
        log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
 | 
			
		||||
@@ -212,7 +199,6 @@ public class GlobalExceptionHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 拦截未知的系统异常
 | 
			
		||||
     */
 | 
			
		||||
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
 | 
			
		||||
    @ExceptionHandler(Throwable.class)
 | 
			
		||||
    public R handleException(Throwable e, HttpServletRequest request) {
 | 
			
		||||
        log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
 | 
			
		||||
 
 | 
			
		||||
@@ -45,12 +45,12 @@ public class R<V> implements Serializable {
 | 
			
		||||
    @Schema(description = "是否成功", example = "true")
 | 
			
		||||
    private boolean success;
 | 
			
		||||
 | 
			
		||||
    /** 状态码 */
 | 
			
		||||
    @Schema(description = "状态码", example = "200")
 | 
			
		||||
    /** 业务状态码 */
 | 
			
		||||
    @Schema(description = "业务状态码", example = "200")
 | 
			
		||||
    private int code;
 | 
			
		||||
 | 
			
		||||
    /** 状态信息 */
 | 
			
		||||
    @Schema(description = "状态信息", example = "操作成功")
 | 
			
		||||
    /** 业务状态信息 */
 | 
			
		||||
    @Schema(description = "业务状态信息", example = "操作成功")
 | 
			
		||||
    private String msg;
 | 
			
		||||
 | 
			
		||||
    /** 返回数据 */
 | 
			
		||||
 
 | 
			
		||||
@@ -50,6 +50,7 @@ import top.charles7c.cnadmin.auth.model.request.LoginRequest;
 | 
			
		||||
import top.charles7c.cnadmin.common.constant.StringConsts;
 | 
			
		||||
import top.charles7c.cnadmin.common.constant.SysConsts;
 | 
			
		||||
import top.charles7c.cnadmin.common.model.dto.LogContext;
 | 
			
		||||
import top.charles7c.cnadmin.common.model.vo.R;
 | 
			
		||||
import top.charles7c.cnadmin.common.util.ExceptionUtils;
 | 
			
		||||
import top.charles7c.cnadmin.common.util.IpUtils;
 | 
			
		||||
import top.charles7c.cnadmin.common.util.ServletUtils;
 | 
			
		||||
@@ -94,7 +95,6 @@ public class LogInterceptor implements HandlerInterceptor {
 | 
			
		||||
        if (null == logDO) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        HandlerMethod handlerMethod = (HandlerMethod)handler;
 | 
			
		||||
        // 记录所属模块
 | 
			
		||||
        this.logModule(logDO, handlerMethod);
 | 
			
		||||
@@ -104,7 +104,6 @@ public class LogInterceptor implements HandlerInterceptor {
 | 
			
		||||
        this.logRequest(logDO, request);
 | 
			
		||||
        // 记录响应信息
 | 
			
		||||
        this.logResponse(logDO, response);
 | 
			
		||||
 | 
			
		||||
        // 保存系统日志
 | 
			
		||||
        SpringUtil.getApplicationContext().publishEvent(logDO);
 | 
			
		||||
    }
 | 
			
		||||
@@ -126,32 +125,30 @@ public class LogInterceptor implements HandlerInterceptor {
 | 
			
		||||
     */
 | 
			
		||||
    private LogDO logElapsedTimeAndException() {
 | 
			
		||||
        LogContext logContext = LogContextHolder.get();
 | 
			
		||||
        if (null == logContext) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            if (null != logContext) {
 | 
			
		||||
                LogDO logDO = new LogDO();
 | 
			
		||||
                logDO.setCreateTime(logContext.getCreateTime());
 | 
			
		||||
                logDO
 | 
			
		||||
                    .setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(logDO.getCreateTime()));
 | 
			
		||||
                logDO.setStatus(LogStatusEnum.SUCCESS);
 | 
			
		||||
 | 
			
		||||
                // 记录错误信息(非未知异常不记录异常详情,只记录错误信息)
 | 
			
		||||
                String errorMsg = logContext.getErrorMsg();
 | 
			
		||||
                if (StrUtil.isNotBlank(errorMsg)) {
 | 
			
		||||
                    logDO.setStatus(LogStatusEnum.FAILURE);
 | 
			
		||||
                    logDO.setErrorMsg(errorMsg);
 | 
			
		||||
                }
 | 
			
		||||
                // 记录异常详情
 | 
			
		||||
                Throwable exception = logContext.getException();
 | 
			
		||||
                if (null != exception) {
 | 
			
		||||
                    logDO.setStatus(LogStatusEnum.FAILURE);
 | 
			
		||||
                    logDO.setExceptionDetail(ExceptionUtil.stacktraceToString(exception, -1));
 | 
			
		||||
                }
 | 
			
		||||
                return logDO;
 | 
			
		||||
            LogDO logDO = new LogDO();
 | 
			
		||||
            logDO.setCreateTime(logContext.getCreateTime());
 | 
			
		||||
            logDO.setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(logDO.getCreateTime()));
 | 
			
		||||
            logDO.setStatus(LogStatusEnum.SUCCESS);
 | 
			
		||||
            // 记录错误信息(非未知异常不记录异常详情,只记录错误信息)
 | 
			
		||||
            String errorMsg = logContext.getErrorMsg();
 | 
			
		||||
            if (StrUtil.isNotBlank(errorMsg)) {
 | 
			
		||||
                logDO.setStatus(LogStatusEnum.FAILURE);
 | 
			
		||||
                logDO.setErrorMsg(errorMsg);
 | 
			
		||||
            }
 | 
			
		||||
            // 记录异常详情
 | 
			
		||||
            Throwable exception = logContext.getException();
 | 
			
		||||
            if (null != exception) {
 | 
			
		||||
                logDO.setStatus(LogStatusEnum.FAILURE);
 | 
			
		||||
                logDO.setExceptionDetail(ExceptionUtil.stacktraceToString(exception, -1));
 | 
			
		||||
            }
 | 
			
		||||
            return logDO;
 | 
			
		||||
        } finally {
 | 
			
		||||
            LogContextHolder.remove();
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -166,7 +163,6 @@ public class LogInterceptor implements HandlerInterceptor {
 | 
			
		||||
        Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class);
 | 
			
		||||
        Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
 | 
			
		||||
        Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
 | 
			
		||||
 | 
			
		||||
        // 例如:@Tag(name = "部门管理") -> 部门管理
 | 
			
		||||
        // (本框架代码规范)例如:@Tag(name = "部门管理 API") -> 部门管理
 | 
			
		||||
        if (null != classTag) {
 | 
			
		||||
@@ -194,7 +190,6 @@ public class LogInterceptor implements HandlerInterceptor {
 | 
			
		||||
    private void logDescription(LogDO logDO, HandlerMethod handlerMethod) {
 | 
			
		||||
        Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
 | 
			
		||||
        Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
 | 
			
		||||
 | 
			
		||||
        // 例如:@Operation(summary="新增部门") -> 新增部门
 | 
			
		||||
        if (null != methodOperation) {
 | 
			
		||||
            logDO.setDescription(StrUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述"));
 | 
			
		||||
@@ -245,14 +240,20 @@ public class LogInterceptor implements HandlerInterceptor {
 | 
			
		||||
    private void logResponse(LogDO logDO, HttpServletResponse response) {
 | 
			
		||||
        int status = response.getStatus();
 | 
			
		||||
        logDO.setStatusCode(status);
 | 
			
		||||
        logDO.setStatus(status >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : logDO.getStatus());
 | 
			
		||||
        logDO.setResponseHeaders(this.desensitize(ServletUtil.getHeadersMap(response)));
 | 
			
		||||
        // 响应体(不记录非 JSON 响应数据)
 | 
			
		||||
        String responseBody = this.getResponseBody(response);
 | 
			
		||||
        if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) {
 | 
			
		||||
            logDO.setResponseBody(responseBody);
 | 
			
		||||
            // 业务状态码优先级高
 | 
			
		||||
            try {
 | 
			
		||||
                R result = JSONUtil.toBean(responseBody, R.class);
 | 
			
		||||
                logDO.setStatusCode(result.getCode());
 | 
			
		||||
                logDO.setStatus(result.isSuccess() ? LogStatusEnum.SUCCESS : LogStatusEnum.FAILURE);
 | 
			
		||||
            } catch (Exception ignored) {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // 操作失败:>= 400
 | 
			
		||||
        logDO.setStatus(status >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : logDO.getStatus());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -269,7 +270,6 @@ public class LogInterceptor implements HandlerInterceptor {
 | 
			
		||||
            if (CollUtil.isEmpty(waitDesensitizeData)) {
 | 
			
		||||
                return desensitizeDataStr;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (String desensitizeProperty : operationLogProperties.getDesensitizeFields()) {
 | 
			
		||||
                waitDesensitizeData.computeIfPresent(desensitizeProperty, (k, v) -> ENCRYPT_SYMBOL);
 | 
			
		||||
                waitDesensitizeData.computeIfPresent(desensitizeProperty.toLowerCase(), (k, v) -> ENCRYPT_SYMBOL);
 | 
			
		||||
@@ -328,13 +328,11 @@ public class LogInterceptor implements HandlerInterceptor {
 | 
			
		||||
        if (!(handler instanceof HandlerMethod) || Boolean.FALSE.equals(operationLogProperties.getEnabled())) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 2、检查是否需要记录内网 IP 操作
 | 
			
		||||
        boolean isInnerIp = IpUtils.isInnerIp(ServletUtil.getClientIP(request));
 | 
			
		||||
        if (isInnerIp && Boolean.FALSE.equals(operationLogProperties.getIncludeInnerIp())) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 3、排除不需要记录系统日志的接口
 | 
			
		||||
        HandlerMethod handlerMethod = (HandlerMethod)handler;
 | 
			
		||||
        Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
 | 
			
		||||
 
 | 
			
		||||
@@ -46,20 +46,10 @@ axios.interceptors.response.use(
 | 
			
		||||
    ) {
 | 
			
		||||
      return response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const res = response.data;
 | 
			
		||||
    if (res.success) {
 | 
			
		||||
      return res;
 | 
			
		||||
    }
 | 
			
		||||
    messageErrorWrapper({
 | 
			
		||||
      content: res.msg || '网络错误',
 | 
			
		||||
      duration: 5 * 1000,
 | 
			
		||||
    });
 | 
			
		||||
    return Promise.reject(new Error(res.msg || '网络错误'));
 | 
			
		||||
  },
 | 
			
		||||
  (error) => {
 | 
			
		||||
    const { response } = error;
 | 
			
		||||
    const res = response.data;
 | 
			
		||||
    if ([401].includes(res.code) && response.config.url !== '/auth/user/info') {
 | 
			
		||||
      modalErrorWrapper({
 | 
			
		||||
        title: '确认退出',
 | 
			
		||||
@@ -68,8 +58,8 @@ axios.interceptors.response.use(
 | 
			
		||||
        escToClose: false,
 | 
			
		||||
        okText: '重新登录',
 | 
			
		||||
        async onOk() {
 | 
			
		||||
          const userStore = useLoginStore();
 | 
			
		||||
          await userStore.logout();
 | 
			
		||||
          const loginStore = useLoginStore();
 | 
			
		||||
          await loginStore.logout();
 | 
			
		||||
          window.location.reload();
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
@@ -79,6 +69,13 @@ axios.interceptors.response.use(
 | 
			
		||||
        duration: 5 * 1000,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    return Promise.reject(new Error(res.msg || '网络错误'));
 | 
			
		||||
  },
 | 
			
		||||
  (error) => {
 | 
			
		||||
    messageErrorWrapper({
 | 
			
		||||
      content: error.msg || '网络错误',
 | 
			
		||||
      duration: 5 * 1000,
 | 
			
		||||
    });
 | 
			
		||||
    return Promise.reject(error);
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@
 | 
			
		||||
      <a-drawer
 | 
			
		||||
        title="日志详情"
 | 
			
		||||
        :visible="visible"
 | 
			
		||||
        :width="580"
 | 
			
		||||
        :width="660"
 | 
			
		||||
        :footer="false"
 | 
			
		||||
        unmount-on-close
 | 
			
		||||
        render-to-body
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user