Giter Club home page Giter Club logo

graceful-response's Introduction

GitHub stars GitHub forks Maven Central

1. 项目介绍

Graceful Response是Spring Boot技术体系下的响应处理解决方案,可以帮助开发者优雅地完成包括统一响应格式数据封装、全局异常处理、错误码填充、异常消息国际化等处理过程,提高开发效率,提高代码质量。

代码现状

代码仓库如下,欢迎star!

  • GitHub
https://github.com/feiniaojin/graceful-response

不怕学不会,B站教学视频https://www.bilibili.com/video/BV1Wm411C7vs/

不怕学不会,B站教学视频https://www.bilibili.com/video/BV1Wm411C7vs/

不怕学不会,B站教学视频https://www.bilibili.com/video/BV1Wm411C7vs/

2. 功能列表

  • 统一返回值封装
  • void返回类型封装
  • 全局异常处理
  • 参数校验错误码
  • 自定义响应体,适应不同项目的需求
  • 断言增强并且填充错误码和异常信息到Response
  • 异常别名,适配外部异常
  • 例外请求放行
  • 第三方组件适配(Swagger、actuator、FastJson序列化等)

更多功能,请到文档中心的项目主页进行了解。

3. 核心应用场景

目前,业界使用Spring Boot进行接口开发时,往往存在效率低下、重复劳动、可读性差等问题。以下伪代码相信大家非常熟悉,我们大部分项目的Controller接口都是这样的。

@Controller
public class Controller {
    
    @GetMapping("/query")
    @ResponseBody
    public Response query(Map<String, Object> paramMap) {
        Response res = new Response();
        try {
            //1.校验params参数合法性,包括非空校验、长度校验等
            if (illegal(paramMap)) {
                res.setCode(1);
                res.setMsg("error");
                return res;
            }
            //2.调用Service的一系列操作,得到查询结果
            Object data = service.query(params);
            //3.将操作结果设置到res对象中
            res.setData(data);
            res.setCode(0);
            res.setMsg("ok");
            return res;
        } catch (Exception e) {
            //4.异常处理:一堆丑陋的try...catch,如果有错误码的,还需要手工填充错误码
            res.setCode(1);
            res.setMsg("error");
            return res;
        }
    }
}

主要体现在以下三个点:

价值低下: Controller层的代码应该尽量简洁,上面的伪代码其实只是为了将数据查询的结果进行封装,使其以统一的格式进行返回。

重复劳动: 以上捕获异常、封装执行结果的操作,每个接口都会进行一次,因此造成大量重复劳动。

可读性低: 上面的核心代码被淹没在许多冗余代码中,很难阅读,如同大海捞针。

Graceful Response可以帮助开发者完成响应数据封装、异常处理、错误码填充等过程,使代码更精简更清晰,可以使开发者有更多的注意力聚焦在业务代码上。效果如下:

@Controller
public class Controller {
    @RequestMapping("/get")
    @ResponseBody
    public UserInfoView get(Long id) {
        log.info("id={}", id);
        return UserInfoView.builder().id(id).name("name" + id).build();
    }
}

4.快速入门

4.1 maven依赖

<dependency>
    <groupId>com.feiniaojin</groupId>
    <artifactId>graceful-response</artifactId>
    <version>{latest.version}</version>
</dependency>

4.2 版本选择

Latest Version

Spring Boot版本 maven版本 graceful-response-example分支
2.x 3.5.2-boot2 3.5.2-boot2
3.x 4.0.1-boot3 4.0.0-boot3

注意,boot2版本的Graceful Response源码由单独的仓库进行维护,boot2和boot3除了支持的SpringBoot版本不一样,其他实现完全一致。boot2版本地址:graceful-response-boot2

4.3 注解开启

在启动类中引入@EnableGracefulResponse注解,即可启用Graceful Response组件。

@EnableGracefulResponse
@SpringBootApplication
public class ExampleApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExampleApplication.class, args);
    }
}

4.4 代码编写

  • Controller

引入Graceful Response后,我们不需要再手工进行查询结果的封装,直接返回实际结果即可,Graceful Response会自动完成封装的操作。

Controller层示例如下。

@Controller
public class Controller {
    @RequestMapping("/get")
    @ResponseBody
    public UserInfoView get(Long id) {
        log.info("id={}", id);
        return UserInfoView.builder().id(id).name("name" + id).build();
    }
}

在示例代码中,Controller层的方法直接返回了UserInfoView对象,没有进行封装的操作,但经过Graceful Response处理后,我们还是得到了以下的响应结果。

{
  "status": {
    "code": "0",
    "msg": "ok"
  },
  "payload": {
    "id": 1,
    "name": "name1"
  }
}

而对于命令操作(Command)尽量不返回数据,因此command操作的方法的返回值应该是void,Graceful Response对于对于返回值类型void的方法,也会自动进行封装。

public class Controller {
    @RequestMapping("/command")
    @ResponseBody
    public void command() {
        //业务操作
    }
}

成功调用该接口,将得到:

{
  "status": {
    "code": "200",
    "msg": "success"
  },
  "payload": {}
}
  • Service层

在引入Graceful Response前,有的开发者在定义Service层的方法时,为了在接口中返回异常码,干脆直接将Service层方法定义为Response,淹没了方法的正常返回值。

传统项目直接返回Response的Service层方法:

/**
 * 直接返回Reponse的Service
 * 不规范
 */
public interface Service {
    public Reponse commandMethod(Command command);
}

Graceful Response引入@ExceptionMapper注解,通过该注解将异常和错误码关联起来,这样Service方法就不需要再维护Response的响应码了,直接抛出业务异常,由Graceful Response进行异常和响应码的关联。 @ExceptionMapper的用法如下。

/**
 * NotFoundException的定义,使用@ExceptionMapper注解修饰
 * code:代表接口的异常码
 * msg:代表接口的异常提示
 */
@ExceptionMapper(code = "1404", msg = "找不到对象")
public class NotFoundException extends RuntimeException {

}

Service接口定义:

public interface QueryService {
    UserInfoView queryOne(Query query);
}

Service接口实现:

public class QueryServiceImpl implements QueryService {
    @Resource
    private UserInfoMapper mapper;

    public UserInfoView queryOne(Query query) {
        UserInfo userInfo = mapper.findOne(query.getId());
        if (Objects.isNull(userInfo)) {
            //这里直接抛自定义异常
            throw new NotFoundException();
        }
        //……后续业务操作
    }
}

当Service层的queryOne方法抛出NotFoundException时,Graceful Response会进行异常捕获,并将NotFoundException对应的异常码和异常信息封装到统一的响应对象中,最终接口返回以下JSON。

{
  "status": {
    "code": "1404",
    "msg": "找不到对象"
  },
  "payload": {}
}

5.文档和示例

5.1 文档中心

https://doc.feiniaojin.com/graceful-response/home.html

点击访问文档中心

5.2 代码示例

  • GitHub
https://github.com/feiniaojin/graceful-response-example

6.交流和反馈

欢迎通过以下二维码联系作者、并加入Graceful Response用户交流群,申请好友时请备注“GR”。

公众号: 悟道领域驱动设计

7.贡献者

8.star

Star History Chart

graceful-response's People

Contributors

aozeyu avatar codezengjie avatar feiniaojin avatar top-lh avatar twolf11 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

graceful-response's Issues

图片验证码接口无法正常使用

使用了本组件后,如果接口使用response方式自定义返回相应内容时,存在兼容问题,比如生成图片验证码的文件流响应给客户端
请问是否考虑兼容?

参数校验异常是否可以增加Field信息

你好,参数校验异常现在是只返回错误信息,如果注解的message没有自定义或者设置的message信息没有写具体的字段名,返回信息就只有错误提示,会有些不明所以
image
请问这里的设计,考虑是所有的message都要手动指定吗?
目前我是通过自定义ValidationExceptionAdvice,指定order(99)覆盖掉原本的Advice,然后修改了msg,是否有更优雅一些的方式?
image

[踩坑],集成knife4j,swagger时出错

image
原因:swagger的接口返回值也被这个框架给封装了
解决: exclude-packages: - org.springdoc.**
希望作者可以将这个添加到默认配置,还可能会有其他第三方包的。
建议改成扫描包(include)的配置,而不是排除包(exclude)

异常处理

整体用下来感觉挺好,唯一不方便的就是GlobalExceptionAdvice处理了系统的所有异常信息,导致系统中的RuntimeException异常信息无法灵活设置相关信息,建议自定义异常继承GracefulResponseException, GlobalExceptionAdvice只拦截GracefulResponseException异常,其他异常交给用户自行处理。

减少异常类的定义

异常code和msg与异常类绑定,业务的不同异常情况下有不同的code和msg,进而异常类定义的太多了。

是否考虑在异常类里面包含code和msg属性,这样业务可以抛出同一个异常 如 BizServiceException,而含有不同的code和msg ?

GlobalExceptionAdvice类空指针异常

GlobalExceptionAdvice类中的属性不知道为什么带@resource注解的属性都没有注入
包括ResponseFactory、GracefulResponseProperties、ResponseStatusFactory

java.lang.NullPointerException: Cannot invoke "com.feiniaojin.gracefulresponse.GracefulResponseProperties.isPrintExceptionInGlobalAdvice()" because "this.gracefulResponseProperties" is null
at com.feiniaojin.gracefulresponse.advice.GlobalExceptionAdvice.exceptionHandler(GlobalExceptionAdvice.java:57) ~[graceful-response-3.1.0.jar:na]

关于自定义异常处理的一个疑问

@ExceptionMapper(code = 1007, msg = "有内鬼,终止交易")
public static final class RatException extends RuntimeException {}

文档中给出的示例如上,一种自定义异常只能返回相同的错误提示msg。但是在实际开发中会遇到自定义异常公用,但是msg不同的场景,对于此种场景该如何实现呢?

支持通过配置排除url路径方式放行请求

框架支持exclude-packages放行包中的请求,但这还远远不够。如果应用中集成了第三方中间或插件,其中有请求,很多情况下是要放行的,调试时我们很容易通过浏览器控制台获取到url路径,获取具体请求url对应的RequestMapping所在的包是困难的,且是没有必要的。
所以期望可以像权限框架(比如spring security,sa-token)一样,直接配置exclude-path/exclude-url放行请求。

通过配置来支持例外请求

例如在swagger中,对请求的处理已经固定下来,加上graceful-reponse后,返回数据的格式变化导致原来的前端逻辑走不下去了。

自定义设置响应头

这是一个非必须的请求,可以通过同类型方法进行替代

  1. 当controller 的返回值为void时
  2. 在controller 的方法如下
    @GetMapping("/wx/user/login")
    public void wxLogin(@RequestParam("code") String code, ServletResponse response) {
...
        ((HttpServletResponse) response).setHeader(JwtUtils.AUTH_HEADER, jwtToken);
}

此时返回的内容为空,所有内容都没有返回

参数级联校验失败时ValidationExceptionAdvice反射获取注解异常

graceful版本:3.1.0

测试代码:

TestController

@RestController
@RequestMapping(value = "/test")
@Slf4j
public class TestController {

    @PostMapping("/validated")
    public void validated(@RequestBody @Validated JobInfoRequest jobInfoRequest) {
        log.info("jobInfoRequest={}", JSON.toJSONString(jobInfoRequest));
    }
}

校验参数对象,其中JobDetail对象进行级联校验

@Data
public class JobInfoRequest {

    @NotBlank(message = "岗位名称不能为空")
    @ValidationStatusCode(code = "501")
    private String jobName;

    @Valid
    @NotNull(message = "详细信息不能为空")
    @ValidationStatusCode(code = "501")
    private JobDetail jobDetail;

}


@Data
public class JobDetail {

    @NotNull(message = "薪水不能为空")
    @Min(value = 3000, message = "不能低于300元")
    @ValidationStatusCode(code = "501")
    private BigDecimal pay;

    @NotNull(message = "招聘人数不能为空")
    @Min(value = 1, message = "至少招聘1人")
    @ValidationStatusCode(code = "501")
    private Integer headCount;

    private Date updateTime;
}

post请求参数

{
  "jobName": "程序员",
  "jobDetail": {
    "pay": 2000.50,
    "headCount": 20,
    "updateTime": "2023-09-30 20:34:08"
  }
}

pay最小值为3000,校验失败,抛出MethodArgumentNotValidException,进入com.feiniaojin.gracefulresponse.advice.ValidationExceptionAdvice全局异常处理器。

在com.feiniaojin.gracefulresponse.advice.ValidationExceptionAdvice#fromBindException第118行,使用反射获取校验失败字段时,外部对象持有的内部对象字段报错,所以fieldName=jobDetail.pay,但是target.getClass()获得的是外部对象JobInfoRequest.class,因此无法获取到jobDetail.pay字段,抛出异常NoSuchFieldException。

image

image

异常信息国际化

作者你好,我对这个框架很感兴趣,想问个问题,目前框架的异常信息支持国际化吗,比如通过这种方式进行国际化

@ExceptionMapper(code = "1007", msg = "{errorcode.aaa}")
public static final class RatException extends RuntimeException {

}

能否适配smart-doc 文档生成工具?

smart-doc是一个轻量级的接口文档生成工具,不同于swagger,这个工具不需要编写侵入式代码来生成文档,而是通过JavaDoc+泛型类型推导实现的。以定义一个用户查询接口举例如下

    /**
     * 根据id获取用户
     * @param userId
     * @return
     * @page
     */
    @GetMapping("/{userId}")
    public CommonResult<User> getUserById(@PathVariable @NotNull(message = "id must be not null") Integer  userId)
    {
        User user = userService.userDetailsById(userId);
        return CommonResult.success(user);
    }

按照这个格式,smart-doc会生成一个get请求的文档如下
image

然而 graceful-responese 也封装了Http接口的返回体 ,也就意味着不需要手动传入泛型,而是框架来封装了,这种情况下是否意味着我无法同时使用2个框架?

通过aop 改变返回值内容,报错类型无法转换

项目中有个需要返回加密的内容,通过aop加密后,并改变类型后,报错
2024-05-14 15:09:37.852 ERROR 21704 --- [nio-8089-exec-3] c.f.g.advice.GlobalExceptionAdvice : Graceful Response:GlobalExceptionAdvice捕获到异常,message=[class java.util.HashMap cannot be cast to class com.baomidou.mybatisplus.extension.plugins.pagination.Page (java.util.HashMap is in module java.base of loader 'bootstrap'; com.baomidou.mybatisplus.extension.plugins.pagination.Page is in unnamed module of loader 'app')]

java.lang.ClassCastException: class java.util.HashMap cannot be cast to class com.baomidou.mybatisplus.extension.plugins.pagination.Page (java.util.HashMap is in module java.base of loader 'bootstrap'; com.baomidou.mybatisplus.extension.plugins.pagination.Page is in unnamed module of loader 'app')
at top.ko25891wan.springboot2.controller.AdminController$$EnhancerBySpringCGLIB$$1.queryAll()
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:529)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:623)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:209)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387)
at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at top.ko25891wan.springboot2.common.crypto.filter.HttpServletRequestInputStreamFilter.doFilter(HttpServletRequestInputStreamFilter.java:35)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168)
at org.apache.catalina.core.StandardContextValve.__invoke(StandardContextValve.java:90)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:41002)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:928)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1794)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:834)

关于外部异常捕获的问题

image
如图所示, 现在捕获外部需要使用@ExceptionAliasFor这个异常, 但假如说有一些异常他是通过异常和异常中的编码一起判断的, 该如何分别捕获呢? 例如sa-token这种
image

ValidationExceptionAdvice类中的exceptionHandler方法判断逻辑疑问

Describe the bug
最近正在阅读您的项目源码,注意到ValidationExceptionAdvice类中的exceptionHandler方法存在如下逻辑

if (e instanceof MethodArgumentNotValidException
                || e instanceof BindException) {
            ResponseStatus responseStatus = this.handleBindException((BindException) e);
            return responseFactory.newInstance(responseStatus);
        }

这里的MethodArgumentNotValidException已经继承了BindException类,所以此处似乎只要保留BindException即可,但是您一定有自己的想法,所以冒昧问问您这里是如何考虑的?
谢谢。

麻烦看一下 #55 的回复,关于加密问题的返回出错问题

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

与spring-boot-starter-actuator冲突导致启动失败

报错如下,根据报错信息显示注入org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping类型的bean的时候出现了两个候选bean,搜了一下另一个是spring-boot-starter-actuator创建的
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'validationAdvice': Injection of resource dependencies failed
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:323) ~[spring-context-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1416) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:950) ~[spring-context-6.0.13.jar:6.0.13]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:616) ~[spring-context-6.0.13.jar:6.0.13]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.5.jar:3.1.5]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:738) ~[spring-boot-3.1.5.jar:3.1.5]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:440) ~[spring-boot-3.1.5.jar:3.1.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-3.1.5.jar:3.1.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.5.jar:3.1.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-3.1.5.jar:3.1.5]
at com.ccfund.msgcenter.MsgCenterApplication.main(MsgCenterApplication.java:17) ~[classes/:na]
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping' available: expected single matching bean but found 2: requestMappingHandlerMapping,controllerEndpointHandlerMapping
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:218) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1395) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:531) ~[spring-context-6.0.13.jar:6.0.13]
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:508) ~[spring-context-6.0.13.jar:6.0.13]
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:659) ~[spring-context-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:270) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:145) ~[spring-beans-6.0.13.jar:6.0.13]
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:320) ~[spring-context-6.0.13.jar:6.0.13]
... 17 common frames omitted

GrValidationExceptionAdvice 处理时能否提供扩展

目前消息直接使用 分号拼接 导致是什么字段出现什么问题不能直观体现
能否提供一个消息自定义处理的接口
还有能否提供一个开关 判断是不是直接把 bind的FieldError返回到前端

建议:框架异常alias关联自定义异常注解支持数组

public @interface ExceptionAliasFor {
String code() default "ERROR";
String msg() default "Poor network quality!";
Class<? extends Throwable> aliasFor();
}
Class<? extends Throwable> aliasFor(); 建议考虑支持数组, 这样springmvc的框架的多种类似异常可以只定义一个异常进行关联。

希望可以支持断言信息统一返回

public boolean deleteSimple(Serializable id)
{
Assert.isTrue(id.equals(65), "ID错误了");
return this.remove(id);
}

例如上面代码,有时候确实需要在业务逻辑中通过断言判断,提示用户简洁的异常信息,这些信息通常是可以让用户自己处理的,由于自己参数不当,或者传递信息有误导致,通过控制的校验函数可能不会那么全面的校验,具体业务的限制我认为是必要的。
所以是否可以支持断言信息的统一封装提供呢?

关于404被包装的疑问

您好。非常感谢您的分享。测试时有一个疑问如下。
当访问路径错误时,正常应该返回http状态404的报错。但是这个报错也被包装成下面这样了:
{
"code": "0",
"msg": "ok",
"data": {
"timestamp": "2023-12-08T01:44:06.482+00:00",
"status": 404,
"error": "Not Found",
"message": "",
"path": "/responseDemo/testExcepti"
}
}
如果按说明文档中“接口开发规范”页的内容,这种层面的报错不应该去包装吧?

错误提示

你好:
我用 graceful-response 作为返回的统一格式, 这个很棒!
但是遇到一个问题: 就是当你不确定异常的时候, 返回的 msg, 并不能把错误显示出来。
----------希望: 有异常的时候, 后端的异常信息, 放到 返回的 msg 里面 msg = e.getMessage()

 比如:  有一 方法

@GetMapping("/me")
public void testNoResponse() {

//    String name = "zhou";
//    Assert.isTrue(name == "wang", "姓名不相等");
int sum = 9 / 0;
System.out.println("me:" + LocalDateTime.now());

}

我希望 msg= 'divide by zero' (后端异常的message, 直接放到 msg 中)
谢谢!

获取异常消息

抛出异常时,现在的方式是从@ExceptionMapper的msg里获取出错消息,那么如果出错信息不一样的话,是不是都要建立对应的Exception类,可以只建一个业务异常类,如果在业务异常类指定其message属性时,就获取该message作为出错消息,@ExceptionMapper的msg则作为message没有设置情况下的默认值

建议支持多种响应风格并存

有些场景上面对接多个业务方,而各个业务方处理响应风格不一
如果使用这个工具无法做到
建议加个注解指定响应的class,如果有注解,该方法或该类的响应风格走指定的
如果没有直接走全局

ValidationStatusCode注解在类属性上时,有概率不生效

Describe the bug
ValidationStatusCode注解添加在属性上时,有概率不生效。

To Reproduce
Steps to reproduce the behavior:
配置文件指定 default-error-code: -1 default-validate-error-code: -2
定义参数对象如下:

@Data
@Accessors(chain = true)
public class StudentAddCmd {
    @ValidationStatusCode
    @NotBlank(message = "姓名不能为空")
    private String name;
    @NotNull
    private Integer age;
    //其他属性...
}

请求接口直接发送{}

Expected behavior
正常情况下,按照代码逻辑应当返回ValidationStatusCode的默认值,即code=1 。但是最连续请求该接口的过程中发现,偶尔出现code=-2
接口为连续请求,中间未重新运行,未重新编译代码。
经过debug发现,List<FieldError> fieldErrors = e.getFieldErrors();获取到的list中的顺序会变,当没有添加注解的属性排在前面的时候,fieldErrors.get(0);无法正确获取添加的注解

Screenshots
image

image

Desktop (please complete the following information):

  • OS: macOS
  • Browser
  • Version jdk21、SpringBoot3.2.0

Additional context

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.