refactor(web): 重构全局响应处理方案

引入 Graceful Response(一个Spring Boot技术栈下的优雅响应处理组件,可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,提高开发效率,提高代码质量)
This commit is contained in:
2024-08-06 23:54:06 +08:00
parent 9ec2e6b981
commit 0b41f2d10c
20 changed files with 419 additions and 777 deletions

View File

@@ -44,6 +44,12 @@
<artifactId>tlog-web-spring-boot-starter</artifactId>
</dependency>
<!-- Graceful Response一个Spring Boot技术栈下的优雅响应处理组件可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程提高开发效率提高代码质量 -->
<dependency>
<groupId>com.feiniaojin</groupId>
<artifactId>graceful-response</artifactId>
</dependency>
<!-- API 文档模块 -->
<dependency>
<groupId>top.continew</groupId>

View File

@@ -17,12 +17,12 @@
package top.continew.starter.web.annotation;
import org.springframework.context.annotation.Import;
import top.continew.starter.web.autoconfigure.exception.GlobalExceptionHandlerAutoConfiguration;
import top.continew.starter.web.autoconfigure.response.GlobalResponseAutoConfiguration;
import java.lang.annotation.*;
/**
* 全局异常错误处理器启用注解
* 全局响应启用注解
*
* @author Charles7c
* @since 1.2.0
@@ -30,6 +30,7 @@ import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({GlobalExceptionHandlerAutoConfiguration.class})
public @interface EnableGlobalExceptionHandler {
@Inherited
@Import({GlobalResponseAutoConfiguration.class})
public @interface EnableGlobalResponse {
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.exception;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import top.continew.starter.web.model.R;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* 全局错误处理器
*
* @author Charles7c
* @since 1.0.0
*/
@RestController
public class GlobalErrorHandler extends BasicErrorController {
private static final Logger log = LoggerFactory.getLogger(GlobalErrorHandler.class);
@Resource
private ObjectMapper objectMapper;
public GlobalErrorHandler(ErrorAttributes errorAttributes,
ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
}
@Override
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> errorAttributeMap = super.getErrorAttributes(request, super.getErrorAttributeOptions(request, MediaType.TEXT_HTML));
String path = (String)errorAttributeMap.get("path");
HttpStatus status = super.getStatus(request);
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
result.setData(path);
try {
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), result);
} catch (IOException e) {
log.error("请求地址 [{}],默认错误处理时发生 IO 异常。", path, e);
}
if (log.isErrorEnabled()) {
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
}
return null;
}
@Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> errorAttributeMap = super.getErrorAttributes(request, super.getErrorAttributeOptions(request, MediaType.ALL));
String path = (String)errorAttributeMap.get("path");
HttpStatus status = super.getStatus(request);
R<Object> result = R.fail(status.value(), (String)errorAttributeMap.get("error"));
result.setData(path);
if (log.isErrorEnabled()) {
log.error("请求地址 [{}],发生错误,错误信息:{}。", path, JSONUtil.toJsonStr(errorAttributeMap));
}
return new ResponseEntity<>(BeanUtil.beanToMap(result), HttpStatus.OK);
}
}

View File

@@ -1,199 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.exception;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
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.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MultipartException;
import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.exception.BadRequestException;
import top.continew.starter.core.exception.BusinessException;
import top.continew.starter.core.exception.GlobalException;
import top.continew.starter.core.exception.ResultInfoInterface;
import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
import top.continew.starter.web.model.R;
import top.continew.starter.core.util.MessageSourceUtils;
/**
* 全局异常处理器
*
* @author Charles7c
* @since 1.1.0
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
private static final String PARAM_FAILED = "请求地址 [{}],参数验证失败。";
@Resource
private I18nProperties i18nProperties;
/**
* 拦截自定义验证异常-错误请求
*/
@ExceptionHandler(BadRequestException.class)
public R<Void> handleBadRequestException(BadRequestException e, HttpServletRequest request) {
log.warn("请求地址 [{}],自定义验证失败。", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
/**
* 拦截校验异常-违反约束异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public R<Void> constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
log.warn(PARAM_FAILED, request.getRequestURI(), e);
String errorMsg = CollUtil.join(e
.getConstraintViolations(), StringConstants.CHINESE_COMMA, ConstraintViolation::getMessage);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截校验异常-绑定异常
*/
@ExceptionHandler(BindException.class)
public R<Void> handleBindException(BindException e, HttpServletRequest request) {
log.warn(PARAM_FAILED, request.getRequestURI(), e);
String errorMsg = CollUtil.join(e
.getAllErrors(), StringConstants.CHINESE_COMMA, DefaultMessageSourceResolvable::getDefaultMessage);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截校验异常-方法参数无效异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
HttpServletRequest request) {
log.warn(PARAM_FAILED, request.getRequestURI(), e);
String errorMsg = CollUtil.join(e
.getAllErrors(), StringConstants.CHINESE_COMMA, DefaultMessageSourceResolvable::getDefaultMessage);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截校验异常-方法参数类型不匹配异常
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
HttpServletRequest request) {
String errorMsg = CharSequenceUtil.format("参数名:[{}],期望参数类型:[{}]", e.getName(), e.getParameter()
.getParameterType());
log.warn("请求地址 [{}],参数转换失败,{}。", request.getRequestURI(), errorMsg, e);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截文件上传异常-超过上传大小限制
*/
@ExceptionHandler(MultipartException.class)
public R<Void> handleRequestTooBigException(MultipartException e, HttpServletRequest request) {
String msg = e.getMessage();
R<Void> defaultFail = R.fail(HttpStatus.BAD_REQUEST.value(), msg);
if (CharSequenceUtil.isBlank(msg)) {
return defaultFail;
}
String sizeLimit;
Throwable cause = e.getCause();
if (null != cause) {
msg = msg.concat(cause.getMessage().toLowerCase());
}
if (msg.contains("size") && msg.contains("exceed")) {
sizeLimit = CharSequenceUtil.subBetween(msg, "the maximum size ", " for");
} else if (msg.contains("larger than")) {
sizeLimit = CharSequenceUtil.subAfter(msg, "larger than ", true);
} else {
return defaultFail;
}
String errorMsg = "请上传小于 %sKB 的文件".formatted(NumberUtil.parseLong(sizeLimit) / 1024);
log.warn("请求地址 [{}],上传文件失败,文件大小超过限制。", request.getRequestURI(), e);
return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg);
}
/**
* 拦截校验异常-请求方式不支持异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
log.error("请求地址 [{}],不支持 [{}] 请求。", request.getRequestURI(), e.getMethod());
return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage());
}
/**
* 拦截业务异常
*/
@ExceptionHandler(BusinessException.class)
public R<Void> handleServiceException(BusinessException e, HttpServletRequest request) {
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}
/**
* 拦截全局应用异常
*/
@ExceptionHandler(GlobalException.class)
public R<Void> handleGlobalException(GlobalException e, HttpServletRequest request) {
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
ResultInfoInterface resultInfo = e.getResultInfo();
// 未开启,直接返回
if (!i18nProperties.getEnabled()) {
return R.fail(resultInfo.getCode(), resultInfo.getDefaultMessage());
}
// 以用户自定的messageKey优先否则枚举当messageKey
String messageKey = StrUtil.blankToDefault(resultInfo.getMessageKey(), resultInfo.toString());
String message = MessageSourceUtils.getMessage(messageKey, resultInfo.getDefaultMessage());
return R.fail(resultInfo.getCode(), message);
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
log.error("请求地址 [{}],发生系统异常。", request.getRequestURI(), e);
return R.fail(e.getMessage());
}
/**
* 拦截未知的系统异常
*/
@ExceptionHandler(Throwable.class)
public R<Void> handleException(Throwable e, HttpServletRequest request) {
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
return R.fail(e.getMessage());
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.exception;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
/**
* 全局异常处理器自动配置
*
* @author Charles7c
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(BasicErrorController.class)
@Import({GlobalExceptionHandler.class, GlobalErrorHandler.class})
@EnableConfigurationProperties(I18nProperties.class)
public class GlobalExceptionHandlerAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandlerAutoConfiguration.class);
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Exception Handler' completed initialization.");
}
}

View File

@@ -24,7 +24,6 @@ import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -44,8 +43,7 @@ public class WebMvcAutoConfiguration implements WebMvcConfigurer {
private static final Logger log = LoggerFactory.getLogger(WebMvcAutoConfiguration.class);
private final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
public WebMvcAutoConfiguration(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter,
ThreadPoolTaskExecutor threadPoolTaskExecutor) {
public WebMvcAutoConfiguration(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
this.mappingJackson2HttpMessageConverter = mappingJackson2HttpMessageConverter;
}

View File

@@ -0,0 +1,150 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
* <p>
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.gnu.org/licenses/lgpl.html
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.response;
import com.feiniaojin.gracefulresponse.ExceptionAliasRegister;
import com.feiniaojin.gracefulresponse.advice.*;
import com.feiniaojin.gracefulresponse.api.ResponseFactory;
import com.feiniaojin.gracefulresponse.api.ResponseStatusFactory;
import com.feiniaojin.gracefulresponse.defaults.DefaultResponseFactory;
import com.feiniaojin.gracefulresponse.defaults.DefaultResponseStatusFactoryImpl;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.ResourceBundleMessageSource;
import top.continew.starter.core.constant.PropertiesConstants;
import top.continew.starter.core.util.GeneralPropertySourceFactory;
import java.util.Locale;
/**
* 全局响应自动配置
*
* @author Charles7c
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(GlobalResponseProperties.class)
@PropertySource(value = "classpath:default-web.yml", factory = GeneralPropertySourceFactory.class)
public class GlobalResponseAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(GlobalResponseAutoConfiguration.class);
/**
* 全局异常处理
*/
@Bean
@ConditionalOnMissingBean
public GrGlobalExceptionAdvice globalExceptionAdvice() {
return new GrGlobalExceptionAdvice();
}
/**
* 全局校验异常处理
*/
@Bean
@ConditionalOnMissingBean
public GrValidationExceptionAdvice validationExceptionAdvice() {
return new GrValidationExceptionAdvice();
}
/**
* 全局响应体处理(非 void
*/
@Bean
@ConditionalOnMissingBean
public GrNotVoidResponseBodyAdvice notVoidResponseBodyAdvice() {
return new GrNotVoidResponseBodyAdvice();
}
/**
* 全局响应体处理void
*/
@Bean
@ConditionalOnMissingBean
public GrVoidResponseBodyAdvice voidResponseBodyAdvice() {
return new GrVoidResponseBodyAdvice();
}
/**
* 响应工厂
*/
@Bean
@ConditionalOnMissingBean
public ResponseFactory responseBeanFactory() {
return new DefaultResponseFactory();
}
/**
* 响应状态工厂
*/
@Bean
@ConditionalOnMissingBean
public ResponseStatusFactory responseStatusFactory() {
return new DefaultResponseStatusFactoryImpl();
}
/**
* 异常别名注册
*/
@Bean
public ExceptionAliasRegister exceptionAliasRegister() {
return new ExceptionAliasRegister();
}
/**
* 响应支持
*/
@Bean
public AdviceSupport adviceSupport() {
return new AdviceSupport();
}
/**
* 国际化支持
*/
@Bean
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
public GrI18nAdvice i18nAdvice() {
return new GrI18nAdvice();
}
/**
* 国际化配置
*/
@Bean
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_RESPONSE, name = "i18n", havingValue = "true")
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("i18n", "i18n/empty-messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setDefaultLocale(Locale.CHINA);
return messageSource;
}
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Web-Global Response' completed initialization.");
}
}

View File

@@ -14,30 +14,18 @@
* limitations under the License.
*/
package top.continew.starter.web.autoconfigure.i18n;
package top.continew.starter.web.autoconfigure.response;
import com.feiniaojin.gracefulresponse.GracefulResponseProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import top.continew.starter.core.constant.PropertiesConstants;
/**
* 国际化配置属性
* 全局响应配置属性
*
* @author Jasmine
* @since 2.2.0
* @author Charles7c
* @since 2.5.0
*/
@ConfigurationProperties(PropertiesConstants.WEB_I18N)
public class I18nProperties {
/**
* 国际化开启 true-开启 false-关闭
*/
private Boolean enabled;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
@ConfigurationProperties(PropertiesConstants.WEB_RESPONSE)
public class GlobalResponseProperties extends GracefulResponseProperties {
}

View File

@@ -16,12 +16,14 @@
package top.continew.starter.web.model;
import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.feiniaojin.gracefulresponse.data.Response;
import com.feiniaojin.gracefulresponse.data.ResponseStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.http.HttpStatus;
import top.continew.starter.web.autoconfigure.response.GlobalResponseProperties;
import java.io.Serial;
import java.io.Serializable;
import java.util.Collections;
/**
* 响应信息
@@ -30,13 +32,25 @@ import java.io.Serializable;
* @since 1.0.0
*/
@Schema(description = "响应信息")
public class R<T> implements Serializable {
public class R implements Response {
@Serial
private static final long serialVersionUID = 1L;
private static final GlobalResponseProperties PROPERTIES = SpringUtil.getBean(GlobalResponseProperties.class);
private static final String DEFAULT_SUCCESS_CODE = PROPERTIES.getDefaultSuccessCode();
private static final String DEFAULT_SUCCESS_MSG = PROPERTIES.getDefaultSuccessMsg();
private static final String DEFAULT_ERROR_CODE = PROPERTIES.getDefaultErrorCode();
private static final String DEFAULT_ERROR_MSG = PROPERTIES.getDefaultErrorMsg();
private static final int SUCCESS_CODE = HttpStatus.OK.value();
private static final int FAIL_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value();
/**
* 状态码
*/
@Schema(description = "状态码", example = "1")
private String code;
/**
* 状态信息
*/
@Schema(description = "状态信息", example = "操作成功")
private String msg;
/**
* 是否成功
@@ -45,153 +59,60 @@ public class R<T> implements Serializable {
private boolean success;
/**
* 业务状态码
* 时间戳
*/
@Schema(description = "业务状态码", example = "200")
private int code;
/**
* 业务状态信息
*/
@Schema(description = "业务状态信息", example = "操作成功")
private String msg;
@Schema(description = "时间戳", example = "1691453288000")
private final Long timestamp = System.currentTimeMillis();
/**
* 响应数据
*/
@Schema(description = "响应数据")
private T data;
private Object data = Collections.emptyMap();
/**
* 时间戳
*/
@Schema(description = "时间戳", example = "1691453288")
private long timestamp = DateUtil.currentSeconds();
private R() {
public R() {
}
private R(boolean success, int code, String msg, T data) {
this.success = success;
this.code = code;
this.msg = msg;
public R(String code, String msg) {
this.setCode(code);
this.setMsg(msg);
}
public R(String code, String msg, Object data) {
this(code, msg);
this.data = data;
}
/**
* 操作成功
*
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> ok() {
return new R<>(true, SUCCESS_CODE, "操作成功", null);
@Override
public void setStatus(ResponseStatus status) {
this.setCode(status.getCode());
this.setMsg(status.getMsg());
}
/**
* 操作成功
*
* @param data 响应数据
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> ok(T data) {
return new R<>(true, SUCCESS_CODE, "操作成功", data);
@Override
@JsonIgnore
public ResponseStatus getStatus() {
return null;
}
/**
* 操作成功
*
* @param msg 业务状态信息
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> ok(String msg) {
return new R<>(true, SUCCESS_CODE, msg, null);
@Override
public void setPayload(Object payload) {
this.data = payload;
}
/**
* 操作成功
*
* @param msg 业务状态信息
* @param data 响应数据
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> ok(String msg, T data) {
return new R<>(true, SUCCESS_CODE, msg, data);
@Override
@JsonIgnore
public Object getPayload() {
return null;
}
/**
* 操作失败
*
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail() {
return new R<>(false, FAIL_CODE, "操作失败", null);
}
/**
* 操作失败
*
* @param msg 业务状态信息
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail(String msg) {
return new R<>(false, FAIL_CODE, msg, null);
}
/**
* 操作失败
*
* @param data 响应数据
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail(T data) {
return new R<>(false, FAIL_CODE, "操作失败", data);
}
/**
* 操作失败
*
* @param msg 业务状态信息
* @param data 响应数据
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail(String msg, T data) {
return new R<>(false, FAIL_CODE, msg, data);
}
/**
* 操作失败
*
* @param code 业务状态码
* @param msg 业务状态信息
* @param <T> 响应数据类型
* @return R /
*/
public static <T> R<T> fail(int code, String msg) {
return new R<>(false, code, msg, null);
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public int getCode() {
public String getCode() {
return code;
}
public void setCode(int code) {
public void setCode(String code) {
this.code = code;
this.success = DEFAULT_SUCCESS_CODE.equals(code);
}
public String getMsg() {
@@ -202,19 +123,73 @@ public class R<T> implements Serializable {
this.msg = msg;
}
public T getData() {
public Object getData() {
return data;
}
public void setData(T data) {
public void setData(Object data) {
this.data = data;
}
public long getTimestamp() {
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
/**
* 操作成功
*
* @return R /
*/
public static R ok() {
return new R(DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MSG);
}
}
/**
* 操作成功
*
* @param data 响应数据
* @return R /
*/
public static R ok(Object data) {
return new R(DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MSG, data);
}
/**
* 操作成功
*
* @param msg 业务状态信息
* @param data 响应数据
* @return R /
*/
public static R ok(String msg, Object data) {
return new R(DEFAULT_SUCCESS_CODE, msg, data);
}
/**
* 操作失败
*
* @return R /
*/
public static R fail() {
return new R(DEFAULT_ERROR_CODE, DEFAULT_ERROR_MSG);
}
/**
* 操作失败
*
* @param code 业务状态码
* @param msg 业务状态信息
* @return R /
*/
public static R fail(String code, String msg) {
return new R(code, msg);
}
}

View File

@@ -0,0 +1,23 @@
--- ### 响应配置
continew-starter.web.response:
# 是否开启国际化默认false
i18n: false
# 响应类全名(配置后 response-style 将不再生效)
response-class-full-name: top.continew.starter.web.model.R
# 自定义成功响应码默认0
default-success-code: 0
# 自定义成功提示默认ok
default-success-msg: ok
# 自定义失败响应码默认1
default-error-code: 1
# 自定义失败提示默认error
default-error-msg: error
# 是否打印异常日志默认false
print-exception-in-global-advice: true
# 是否将原生异常错误信息填充到状态信息中默认false
origin-exception-using-detail-message: true
# 例外包路径(支持数字, * 和 ** 通配符匹配),该包路径下的 Controller 将被忽略处理
exclude-packages:
- io.swagger.**
- org.springdoc.**
- org.springframework.boot.actuate.*