refactor: 💥 分离 HTTP 状态码和业务状态码

1.传输正常的情况下无论业务是否有异常,HTTP 状态码始终为 200
2.防止非 HTTPS 情况下出现运营商劫持(例如:404)
This commit is contained in:
Charles7c 2023-09-10 22:35:50 +08:00
parent a3082e72a9
commit b3b6446433
6 changed files with 44 additions and 63 deletions

View File

@ -70,7 +70,7 @@ public class GlobalErrorHandler extends BasicErrorController {
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error")); R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
result.setData(path); result.setData(path);
try { try {
response.setStatus(status.value()); response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), result); objectMapper.writeValue(response.getWriter(), result);
} catch (IOException e) { } catch (IOException e) {
@ -89,6 +89,6 @@ public class GlobalErrorHandler extends BasicErrorController {
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error")); R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
result.setData(path); result.setData(path);
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap)); log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
return new ResponseEntity<>(BeanUtil.beanToMap(result), status); return new ResponseEntity<>(BeanUtil.beanToMap(result), HttpStatus.OK);
} }
} }

View File

@ -30,7 +30,6 @@ import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler; 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.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MaxUploadSizeExceededException;
@ -62,7 +61,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截自定义验证异常-错误请求 * 拦截自定义验证异常-错误请求
*/ */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BadRequestException.class) @ExceptionHandler(BadRequestException.class)
public R handleBadRequestException(BadRequestException e, HttpServletRequest request) { public R handleBadRequestException(BadRequestException e, HttpServletRequest request) {
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e); log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
@ -73,7 +71,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截校验异常-违反约束异常 * 拦截校验异常-违反约束异常
*/ */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class) @ExceptionHandler(ConstraintViolationException.class)
public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) { public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e); log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
@ -85,7 +82,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截校验异常-绑定异常 * 拦截校验异常-绑定异常
*/ */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class) @ExceptionHandler(BindException.class)
public R handleBindException(BindException e, HttpServletRequest request) { public R handleBindException(BindException e, HttpServletRequest request) {
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e); log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
@ -97,7 +93,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截校验异常-方法参数无效异常 * 拦截校验异常-方法参数无效异常
*/ */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class) @ExceptionHandler(MethodArgumentNotValidException.class)
public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e); log.warn("请求地址 [{}],参数验证失败。", request.getRequestURI(), e);
@ -110,7 +105,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截校验异常-方法参数类型不匹配异常 * 拦截校验异常-方法参数类型不匹配异常
*/ */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentTypeMismatchException.class) @ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, public R handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
HttpServletRequest request) { HttpServletRequest request) {
@ -123,7 +117,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截文件上传异常-超过上传大小限制 * 拦截文件上传异常-超过上传大小限制
*/ */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MaxUploadSizeExceededException.class) @ExceptionHandler(MaxUploadSizeExceededException.class)
public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) { public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e); log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
@ -136,7 +129,6 @@ public class GlobalExceptionHandler {
/** /**
* 认证异常-登录认证 * 认证异常-登录认证
*/ */
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(NotLoginException.class) @ExceptionHandler(NotLoginException.class)
public R handleNotLoginException(NotLoginException e, HttpServletRequest request) { public R handleNotLoginException(NotLoginException e, HttpServletRequest request) {
log.error("请求地址 [{}],认证失败,无法访问系统资源。", request.getRequestURI(), e); log.error("请求地址 [{}],认证失败,无法访问系统资源。", request.getRequestURI(), e);
@ -159,7 +151,6 @@ public class GlobalExceptionHandler {
/** /**
* 认证异常-权限认证 * 认证异常-权限认证
*/ */
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(NotPermissionException.class) @ExceptionHandler(NotPermissionException.class)
public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) { public R handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
log.error("请求地址 [{}],权限码校验失败。", request.getRequestURI(), e); log.error("请求地址 [{}],权限码校验失败。", request.getRequestURI(), e);
@ -169,7 +160,6 @@ public class GlobalExceptionHandler {
/** /**
* 认证异常-角色认证 * 认证异常-角色认证
*/ */
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(NotRoleException.class) @ExceptionHandler(NotRoleException.class)
public R handleNotRoleException(NotRoleException e, HttpServletRequest request) { public R handleNotRoleException(NotRoleException e, HttpServletRequest request) {
log.error("请求地址 [{}],角色权限校验失败。", request.getRequestURI(), e); log.error("请求地址 [{}],角色权限校验失败。", request.getRequestURI(), e);
@ -179,7 +169,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截校验异常-请求方式不支持异常 * 拦截校验异常-请求方式不支持异常
*/ */
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class) @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) { public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
LogContextHolder.setErrorMsg(e.getMessage()); LogContextHolder.setErrorMsg(e.getMessage());
@ -190,7 +179,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截业务异常 * 拦截业务异常
*/ */
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(ServiceException.class) @ExceptionHandler(ServiceException.class)
public R handleServiceException(ServiceException e, HttpServletRequest request) { public R handleServiceException(ServiceException e, HttpServletRequest request) {
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e); log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
@ -201,7 +189,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截未知的运行时异常 * 拦截未知的运行时异常
*/ */
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(RuntimeException.class) @ExceptionHandler(RuntimeException.class)
public R handleRuntimeException(RuntimeException e, HttpServletRequest request) { public R handleRuntimeException(RuntimeException e, HttpServletRequest request) {
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e); log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
@ -212,7 +199,6 @@ public class GlobalExceptionHandler {
/** /**
* 拦截未知的系统异常 * 拦截未知的系统异常
*/ */
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class) @ExceptionHandler(Throwable.class)
public R handleException(Throwable e, HttpServletRequest request) { public R handleException(Throwable e, HttpServletRequest request) {
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e); log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);

View File

@ -45,12 +45,12 @@ public class R<V> implements Serializable {
@Schema(description = "是否成功", example = "true") @Schema(description = "是否成功", example = "true")
private boolean success; private boolean success;
/** 状态码 */ /** 业务状态码 */
@Schema(description = "状态码", example = "200") @Schema(description = "业务状态码", example = "200")
private int code; private int code;
/** 状态信息 */ /** 业务状态信息 */
@Schema(description = "状态信息", example = "操作成功") @Schema(description = "业务状态信息", example = "操作成功")
private String msg; private String msg;
/** 返回数据 */ /** 返回数据 */

View File

@ -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.StringConsts;
import top.charles7c.cnadmin.common.constant.SysConsts; import top.charles7c.cnadmin.common.constant.SysConsts;
import top.charles7c.cnadmin.common.model.dto.LogContext; 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.ExceptionUtils;
import top.charles7c.cnadmin.common.util.IpUtils; import top.charles7c.cnadmin.common.util.IpUtils;
import top.charles7c.cnadmin.common.util.ServletUtils; import top.charles7c.cnadmin.common.util.ServletUtils;
@ -94,7 +95,6 @@ public class LogInterceptor implements HandlerInterceptor {
if (null == logDO) { if (null == logDO) {
return; return;
} }
HandlerMethod handlerMethod = (HandlerMethod)handler; HandlerMethod handlerMethod = (HandlerMethod)handler;
// 记录所属模块 // 记录所属模块
this.logModule(logDO, handlerMethod); this.logModule(logDO, handlerMethod);
@ -104,7 +104,6 @@ public class LogInterceptor implements HandlerInterceptor {
this.logRequest(logDO, request); this.logRequest(logDO, request);
// 记录响应信息 // 记录响应信息
this.logResponse(logDO, response); this.logResponse(logDO, response);
// 保存系统日志 // 保存系统日志
SpringUtil.getApplicationContext().publishEvent(logDO); SpringUtil.getApplicationContext().publishEvent(logDO);
} }
@ -126,14 +125,14 @@ public class LogInterceptor implements HandlerInterceptor {
*/ */
private LogDO logElapsedTimeAndException() { private LogDO logElapsedTimeAndException() {
LogContext logContext = LogContextHolder.get(); LogContext logContext = LogContextHolder.get();
if (null == logContext) {
return null;
}
try { try {
if (null != logContext) {
LogDO logDO = new LogDO(); LogDO logDO = new LogDO();
logDO.setCreateTime(logContext.getCreateTime()); logDO.setCreateTime(logContext.getCreateTime());
logDO logDO.setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(logDO.getCreateTime()));
.setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(logDO.getCreateTime()));
logDO.setStatus(LogStatusEnum.SUCCESS); logDO.setStatus(LogStatusEnum.SUCCESS);
// 记录错误信息非未知异常不记录异常详情只记录错误信息 // 记录错误信息非未知异常不记录异常详情只记录错误信息
String errorMsg = logContext.getErrorMsg(); String errorMsg = logContext.getErrorMsg();
if (StrUtil.isNotBlank(errorMsg)) { if (StrUtil.isNotBlank(errorMsg)) {
@ -147,11 +146,9 @@ public class LogInterceptor implements HandlerInterceptor {
logDO.setExceptionDetail(ExceptionUtil.stacktraceToString(exception, -1)); logDO.setExceptionDetail(ExceptionUtil.stacktraceToString(exception, -1));
} }
return logDO; return logDO;
}
} finally { } finally {
LogContextHolder.remove(); LogContextHolder.remove();
} }
return null;
} }
/** /**
@ -166,7 +163,6 @@ public class LogInterceptor implements HandlerInterceptor {
Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class); Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class);
Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class); Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class);
Log methodLog = handlerMethod.getMethodAnnotation(Log.class); Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
// 例如@Tag(name = "部门管理") -> 部门管理 // 例如@Tag(name = "部门管理") -> 部门管理
// 本框架代码规范例如@Tag(name = "部门管理 API") -> 部门管理 // 本框架代码规范例如@Tag(name = "部门管理 API") -> 部门管理
if (null != classTag) { if (null != classTag) {
@ -194,7 +190,6 @@ public class LogInterceptor implements HandlerInterceptor {
private void logDescription(LogDO logDO, HandlerMethod handlerMethod) { private void logDescription(LogDO logDO, HandlerMethod handlerMethod) {
Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class); Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class);
Log methodLog = handlerMethod.getMethodAnnotation(Log.class); Log methodLog = handlerMethod.getMethodAnnotation(Log.class);
// 例如@Operation(summary="新增部门") -> 新增部门 // 例如@Operation(summary="新增部门") -> 新增部门
if (null != methodOperation) { if (null != methodOperation) {
logDO.setDescription(StrUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述")); logDO.setDescription(StrUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述"));
@ -245,14 +240,20 @@ public class LogInterceptor implements HandlerInterceptor {
private void logResponse(LogDO logDO, HttpServletResponse response) { private void logResponse(LogDO logDO, HttpServletResponse response) {
int status = response.getStatus(); int status = response.getStatus();
logDO.setStatusCode(status); logDO.setStatusCode(status);
logDO.setStatus(status >= HttpStatus.HTTP_BAD_REQUEST ? LogStatusEnum.FAILURE : logDO.getStatus());
logDO.setResponseHeaders(this.desensitize(ServletUtil.getHeadersMap(response))); logDO.setResponseHeaders(this.desensitize(ServletUtil.getHeadersMap(response)));
// 响应体不记录非 JSON 响应数据 // 响应体不记录非 JSON 响应数据
String responseBody = this.getResponseBody(response); String responseBody = this.getResponseBody(response);
if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) { if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) {
logDO.setResponseBody(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)) { if (CollUtil.isEmpty(waitDesensitizeData)) {
return desensitizeDataStr; return desensitizeDataStr;
} }
for (String desensitizeProperty : operationLogProperties.getDesensitizeFields()) { for (String desensitizeProperty : operationLogProperties.getDesensitizeFields()) {
waitDesensitizeData.computeIfPresent(desensitizeProperty, (k, v) -> ENCRYPT_SYMBOL); waitDesensitizeData.computeIfPresent(desensitizeProperty, (k, v) -> ENCRYPT_SYMBOL);
waitDesensitizeData.computeIfPresent(desensitizeProperty.toLowerCase(), (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())) { if (!(handler instanceof HandlerMethod) || Boolean.FALSE.equals(operationLogProperties.getEnabled())) {
return false; return false;
} }
// 2检查是否需要记录内网 IP 操作 // 2检查是否需要记录内网 IP 操作
boolean isInnerIp = IpUtils.isInnerIp(ServletUtil.getClientIP(request)); boolean isInnerIp = IpUtils.isInnerIp(ServletUtil.getClientIP(request));
if (isInnerIp && Boolean.FALSE.equals(operationLogProperties.getIncludeInnerIp())) { if (isInnerIp && Boolean.FALSE.equals(operationLogProperties.getIncludeInnerIp())) {
return false; return false;
} }
// 3排除不需要记录系统日志的接口 // 3排除不需要记录系统日志的接口
HandlerMethod handlerMethod = (HandlerMethod)handler; HandlerMethod handlerMethod = (HandlerMethod)handler;
Log methodLog = handlerMethod.getMethodAnnotation(Log.class); Log methodLog = handlerMethod.getMethodAnnotation(Log.class);

View File

@ -46,20 +46,10 @@ axios.interceptors.response.use(
) { ) {
return response; return response;
} }
const res = response.data; const res = response.data;
if (res.success) { if (res.success) {
return res; 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') { if ([401].includes(res.code) && response.config.url !== '/auth/user/info') {
modalErrorWrapper({ modalErrorWrapper({
title: '确认退出', title: '确认退出',
@ -68,8 +58,8 @@ axios.interceptors.response.use(
escToClose: false, escToClose: false,
okText: '重新登录', okText: '重新登录',
async onOk() { async onOk() {
const userStore = useLoginStore(); const loginStore = useLoginStore();
await userStore.logout(); await loginStore.logout();
window.location.reload(); window.location.reload();
}, },
}); });
@ -79,6 +69,13 @@ axios.interceptors.response.use(
duration: 5 * 1000, duration: 5 * 1000,
}); });
} }
return Promise.reject(new Error(res.msg || '网络错误'));
},
(error) => {
messageErrorWrapper({
content: error.msg || '网络错误',
duration: 5 * 1000,
});
return Promise.reject(error); return Promise.reject(error);
} }
); );

View File

@ -117,7 +117,7 @@
<a-drawer <a-drawer
title="日志详情" title="日志详情"
:visible="visible" :visible="visible"
:width="580" :width="660"
:footer="false" :footer="false"
unmount-on-close unmount-on-close
render-to-body render-to-body