当前位置:   article > 正文

SpringBoot gRPC同步异常处理_net.devh.boot.grpc.server.service.grpcservice 报红

net.devh.boot.grpc.server.service.grpcservice 报红


前言

前提条件

  • gRPC使用一元通信
  • SpringBoot(Cloud)项目

目标

  • gRPC服务端的业务异常需要透传到gRPC客户端,客户端的根据业务异常展现给用户

重要版本说明

  • SpringBoot : 2.7.1
  • grpc-server-spring-boot-starter : 2.13.1.RELEASE
  • grpc-client-spring-boot-starter : 2.13.1.RELEASE
  • protobuf : 3.21.2

一、proto

proto生成后的java类就不贴了,太多,用插件自动生成就行

syntax = "proto3";
option java_multiple_files = false;
option java_outer_classname = "JwtProto";
service AuthService {
  // 一元rpc
  rpc validToken(JwtRequest) returns (R){}
}

message JwtRequest {
  string token = 1;
  bool valid_data = 2;
  string func_id = 3;
}

message R {
  int32 code = 1;
}
// 自定义异常信息
message ErrorInfo {
  int32 code = 1;
  string msg = 2;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

二、gRPC Server端

1.Server 业务代码

import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;

@Slf4j
@GrpcService
public class AuthService extends AuthServiceGrpc.AuthServiceImplBase {

    @Override
    public void validToken(JwtProto.JwtRequest req, StreamObserver<JwtProto.R> respObserver) {
        String token = req.getToken();
        String funcId = req.getFuncId();
        log.info("validToken,[funcId:{}],[token:{}]", funcId, token);
        // 假设这里需要抛出业务异常
        if (xxx) {
            throw new BusinessException(ResultEnum.ERR_SYSTEM_GRPC);
        }
        JwtProto.R r = JwtProto.R.newBuilder().setCode(ResultEnum.SUCCESS.getCode()).build();
        respObserver.onNext(r);
        respObserver.onCompleted();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

BusinessException:自定义业务异常
ResultEnum:全局业务返回枚举定义,与功能无关

@Getter
@AllArgsConstructor
public enum ResultEnum {

    SUCCESS(0, ""),

    /**
     * 系统级错误(0-50]
     */
    ERR(1, "系统错误"),
    ERR_SYSTEM_GRPC(2, "gRPC异常"),
    ;

    private final Integer code;
    private final String msg;

    public static ResultEnum of(Integer value) {
        if (value != null) {
            for (ResultEnum e : ResultEnum.values()) {
                if (e.getCode().equals(value)) {
                    return e;
                }
            }
        }
        return null;
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

2.Server 全局异常处理

import com.google.protobuf.Any;
import com.google.rpc.Code;
import com.google.rpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.StatusProto;
import net.devh.boot.grpc.server.advice.GrpcAdvice;
import net.devh.boot.grpc.server.advice.GrpcExceptionHandler;

@GrpcAdvice
public class GlobalGrpcExceptionHandler {

    @GrpcExceptionHandler(BusinessException.class)
    public StatusRuntimeException businessExceptionHandle(BusinessException e) {
        JwtProto.ErrorInfo errorInfo = JwtProto.ErrorInfo.newBuilder().setCode(e.getCode()).setMsg(e.getMsg()).build();
        var status = Status.newBuilder()
            .setCode(Code.UNKNOWN.getNumber())
            .setMessage(e.getMsg())
            .addDetails(Any.pack(errorInfo))
            .build();
        return StatusProto.toStatusRuntimeException(status);
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在全局异常处理的地方封装自定义的ErrorInfo对象,然后提供给客户端

三、gRPC Client端

1.Client 业务代码

@Slf4j
@Component
public class JwtUtils {

	@GrpcClient("auth")
    private AuthServiceGrpc.AuthServiceStub authStub;
    private static AuthServiceGrpc.AuthServiceStub authStaticStub;

    @PostConstruct
    public void init() {
        authStaticStub = authStub;
    }
	
	public static void validateToken() {
		// 简化传参
		authStaticStub.validToken(JwtProto.JwtRequest.newBuilder().setToken("xxx123").build());
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2.Client 全局异常处理

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.rpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.StatusProto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Optional;

@Slf4j
@ControllerAdvice
public class GlobalException {

    @ResponseBody
    @ExceptionHandler(StatusRuntimeException.class)
    public R<?> statusRuntimeException(StatusRuntimeException grpcException) {
        log.error("gRPCException, {}", grpcException.getMessage(), grpcException);
        Status status = StatusProto.fromThrowable(grpcException);
        if (status != null) {
            Optional<JwtProto.ErrorInfo> first = status.getDetailsList().stream().filter(d -> d.is(JwtProto.ErrorInfo.class)).map(d -> {
                try {
                    return d.unpack(JwtProto.ErrorInfo.class);
                } catch (InvalidProtocolBufferException ex) {
                    throw new RuntimeException(ex);
                }
            }).findFirst();
            if (first.isPresent()) {
                JwtProto.ErrorInfo error = first.get();
                return R.fail(ResultEnum.of(error.getCode()), error.getMsg());
            }
        }
        return R.fail(ResultEnum.ERR_SYSTEM_GRPC);
    }
    
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

client的全局异常主要是对响应给用户的处理


总结

  • 使用了SpringBoot的gRPC异常处理,可以做到与处理普通controller异常一样,grpc-spring-boot提供了类似@ControllerAdvice的注解 @GrpcAdvice@GrpcExceptionHandler
  • 该处理方式其实对服务端流、客户端流、双向流也有效,在客户端都能拿到服务端的自定义业务异常,只不过思想要转变,处理方式也要转变一下。首要的就是思考什么业务才需要使用异步,而不是随便拿到个业务统统使用异步处理。异步中处理异常可分为两种:1.异步单独处理异常,与主线程无关。2.在异步中同步拿到异常,主线程需等待异步执行完或者抛出了异常,才会继续往下走,否则等待(和使用BlockingStub几乎没区别),本文主要是处理同步中的异常。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/145757
推荐阅读
相关标签
  

闽ICP备14008679号