基于grpc从零开始搭建一个准生产分布式应用(3) - GRPC实现

本章开始会进入GRPC子专题,先实现前面章节中提到的例子。然后就使用的知识点展开全面的描述。本章代码任务:1、实现一个简单的GRPC服务;2、实现GRPC拦截器。

本章的代码承接上一章的代码进行迭代。因模块间存在相互依赖关系,读者一定先按笔者讲述的顺序操作,否则最后程序可能由于依赖问题导致不能运行;

一、准备工作

因为本专题定位于准生产环境,所以我们在代码上也会按规范严格要求一下,本章涉及的准备工作就是在common模块优先定义一些辅助工具类。因代码比较多,详细可查看:

  • ​​基于grpc从零开始搭建一个准生产分布式应用(8) - 01 - 附:GRPC公共库源码​​
  • ​​基于grpc从零开始搭建一个准生产分布式应用(7) - 01 - 附:GRPC拦截器源码​​

二、API接口定义

修改【base-grpc-framework-api】模块,我们用proto方式来实现,首先要修改pom.xml文件引入必要的依赖,再定义一个.proto文件。proto的使用在下一章节会详细介绍,本章了解下必要的知识点即可。

2.1、pom.xml文件修改

这块没有需要注意的地方,用下列内容覆盖上一章中的源码即可。源码中的dependency全部用provided来修饰的原因是,在系统运行时会用到编译后的源码,这里的依赖只是用于辅助编译用。



    
        base-grpc-framework-parent
        com.zd
        1.0-SNAPSHOT
    
    4.0.0

    base-grpc-framework-api
    jar

    
        
            io.grpc
            grpc-stub
            provided
        
        
            io.grpc
            grpc-protobuf
            provided
        

        
            com.google.protobuf
            protobuf-java
            provided
        

        
            com.google.protobuf
            protobuf-java-util
            provided
        

    
    
        
        
            
                kr.motd.maven
                os-maven-plugin
                ${maven.kr.motd}
            
        

        
            
            
                org.xolstice.maven.plugins
                protobuf-maven-plugin
                ${maven.org.xolstice}
                
                    ${project.basedir}/src/main/proto
                    com.google.protobuf:protoc:${protobuf.java.version}:exe:${os.detected.classifier}
                    grpc-java
                    io.grpc:protoc-gen-grpc-java:${io.grpc.version}:exe:${os.detected.classifier}
                
                
                    
                        
                            compile
                            compile-custom
                        
                    
                
            
        
    

2.2、systemlog.proto文件定义

文件路径:src/main/proto/sysrecord.proto,一个经验就是,定义入参时要用包装类型方便参数验证,如下面的的CreateSysRecordRequest定义。返回结果用简单类型防止空指针异常,如下面的ListSysRecordResponse定义。

syntax = "proto3";

package com.zd.baseframework.core.api.sysrecord;

import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";

option java_package = "com.zd.baseframework.core.api.systemlog";
option java_outer_classname = "SystemLogProto";
option java_multiple_files = true;

service ISystemLogService{

  //创建
  rpc CreateSystemLog (CreateSystemLogRequest) returns (SystemLogOperatorResponse);

  //查询
  rpc ListSystemLogByCondition (ListSystemLogRequest) returns (ListSystemLogResponse);

}
//请求参数
message CreateSystemLogRequest{
  google.protobuf.StringValue biz_id = 1;
  google.protobuf.Int64Value user_id = 2;
  google.protobuf.StringValue code = 4;
  google.protobuf.StringValue custom_code = 5;
}

message ListSystemLogRequest{
  google.protobuf.StringValue biz_id = 1;
  google.protobuf.Int64Value user_id = 2;
  google.protobuf.Int32Value code = 3;
}

//返回结果
message  SystemLogOperatorResponse{
  optional int32 status = 1;
  optional string message = 2;
}

message  ListSystemLogResponse{
  repeated SystemLogDto data = 1;
}

message SystemLogDto {
  int64 id = 1;
  string biz_id = 2;
  int64 user_id = 3;
  string track_uid = 4;
  string code = 5;
  string custom_code = 6;
  int32 state = 7;
  google.protobuf.Timestamp code_name = 8;
  google.protobuf.Timestamp utime = 9;
}

2.3、proto文件编译

把.proto文件编译成java代码,在idea中的操作过程共分为三步:

  1. 在pom.xml中配置proto插件;
  2. 执行protobuf:complie生成接口;
  3. 执行protobuf:complile-custom生成实体类;

先确定已经按2.2节内容正确配置了protobuf-maven-plugin插件后,然后先后双击下图中红框的内容:

基于grpc从零开始搭建一个准生产分布式应用(3) - GRPC实现_第1张图片

最后打开源码的target文件夹,会看到生成的Java源码文件,如下图所示,grpc-java文件夹中存储的是接口定义即proto文件中service标签中定义的内容,java文件中存储的是message标签定义的内容,至此api模块的所有修改就完成了:

基于grpc从零开始搭建一个准生产分布式应用(3) - GRPC实现_第2张图片

三、GPRC实现-服务端

本章我们会传承上一章节的代码,需改动下图中标红的模块。服务端的实现稍复杂一些,同样也要改一些pom配置,本次我们会实现两个接口,同学们可以了解下grpc本地调用的实现方法,同样grpc详细的内容也会在后续章节详细展开。

基于grpc从零开始搭建一个准生产分布式应用(3) - GRPC实现_第3张图片

  • application模块:配置配置参数;
  • core模块:实现一个grpc和一个controler服务,同时暴露两种协议的接口,熟悉grpc的本地调用;

3.1、pom.xml文件修改

修改【base-grpc-framework-core】模块,这里没有太多需要注意的内容,复制即可;下面源码中多了lombok和mapstruft的内容,暂时保留吧,后面也会用到了。



    
        base-grpc-framework-parent
        com.zd
        1.0-SNAPSHOT
    
    4.0.0

    base-grpc-framework-core

    
        0.1.0
    

    
        
        
            net.devh
            grpc-server-spring-boot-starter
        
        
            com.google.protobuf
            protobuf-java-util
        
        
            com.google.protobuf
            protobuf-java
        

        
        
            net.devh
            grpc-client-spring-boot-starter
        

        
        
            com.zd
            base-grpc-framework-common
            ${project.parent.version}
            
                
                    slf4j-log4j12
                    org.slf4j
                
            
        
        
            com.zd
            base-grpc-framework-api
            ${project.parent.version}
        

        
        
            com.alibaba.boot
            nacos-config-spring-boot-starter
        


        

        
        
            org.projectlombok
            lombok
            compile
        

        
        
            cn.hutool
            hutool-all
        

        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        
    

    

        
            
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.1
                
                    ${maven.compiler.source}
                    ${maven.compiler.target}
                    
                        
                            org.projectlombok
                            lombok
                            ${lombok.version}
                        
                        
                            org.mapstruct
                            mapstruct-processor
                            ${mapstruct.version}
                        

                        
                        
                            org.projectlombok
                            lombok-mapstruct-binding
                            ${lombok.mapstruct.binding.version}
                        
                    
                
            
        
    

3.2、Gprc服务实现

修改【base-grpc-framework-core】模块,类路径:com.zd.baseframework.core.core.sysrecord.api.impl.ISysRecordServiceApiImpl ,代码实现如下,这里我们只实现一个创建接口,本类创建后还需要在application.yml中进行一些配置,后面一起来讲;

下面源码唯一注意的是一定要标上@GrpcService注解,对于源码中没有找到的类全在common包中,可先按附录方式创建好必要的辅助类。

如果发现找不到API中的类,可点击右侧MAVEN窗口刷新工程。

@GrpcService
@Slf4j
public class SystemLogServiceApiImpl extends ISystemLogServiceGrpc.ISystemLogServiceImplBase {

    @Override
    public void createSystemLog(CreateSystemLogRequest request, StreamObserver responseObserver)  {
        String bizId = request.hasBizId()?request.getBizId().getValue():null;
        Long userId  = request.hasUserId()?request.getUserId().getValue():null;
        String code = request.hasCode()?request.getCode().getValue():null;
        String customCode = request.hasCustomCode()?request.getCustomCode().getValue():null;

        String trackLog = LogGenerator.trackLog();

        try{
            //打印原始入参
            log.info(trackLog
                    + " grpcReqParam=" + JsonFormat.printer().omittingInsignificantWhitespace().print(request)
            );

            //入参验证
            if(StrUtil.isEmpty(bizId)
                    || null==userId
                    || StrUtil.isEmpty(code) ){
                throw new AppException("非法参数");
            }

            SysRecordOperatorResponse response  = SysRecordOperatorResponse.newBuilder()
                    .setStatus(ResponseConst.SUCCESS)
                    .setMessage(ResponseConst.Msg.SUCCESS)
                    .build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
            responseObserver.onCompleted();

        }catch (AppException e) {
            log.error(e.getMessage());
            responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException());
        }
        catch(Exception e){
            log.error(e.getMessage());
            responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
        }
    }

    @Override
    public void listSystemLogByCondition(ListSystemLogRequest request, StreamObserver responseObserver) {

        String trackLog = LogGenerator.trackLog();

        try{
            //打印原始入参
            log.info(trackLog
                    + " grpcReqParam=" + JsonFormat.printer().omittingInsignificantWhitespace().print(request)
            );


            ListSystemLogResponse response = ListSystemLogResponse.newBuilder().build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();

        }catch (AppException e) {
            log.error(e.getMessage());
            responseObserver.onError(Status.INVALID_ARGUMENT.withDescription(e.getMessage()).asException());
        }
        catch(Exception e){
            log.error(e.getMessage());
            responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asException());
        }
    }
}

3.3、Controller接口实现

修改【base-grpc-framework-core】模块,实现本地grpc调用,本类创建后还需要在application.yml中进行一些配置,后面一起来讲;类路径:com.zd.baseframework.core.controller.core.SystemLogController.java。代码如下:

注意@GrpcClient("inProcess")和@RestController注解不要忘记。

@Slf4j
@RestController
@RequestMapping("/systemlog")
public class SystemLogController {

    @GrpcClient("inProcess")
    private ISystemLogServiceGrpc.ISystemLogServiceBlockingStub iSysRecordServiceBlockingStub;

    @GetMapping("/v1/create_systemlog")
    public BaseResponse createSystemLog(
            @RequestParam(value="biz_id") String bizId,
            @RequestParam(value="user_id") Long userId,
            @RequestParam(value="code") String code,
            @RequestParam(value="custom_code", required=false) String customCode){
        try{
            //构建请求参数
            CreateSystemLogRequest createSysRecordRequest  = CreateSystemLogRequest.newBuilder()
                    .setBizId(StringValue.of(bizId))
                    .setUserId(Int64Value.of(userId))
                    .setCode(StringValue.of(code))
                    .setCustomCode(StringValue.of(customCode))
                    .build();

            //正常此处需要判断response返回的state值
            SystemLogOperatorResponse response = iSysRecordServiceBlockingStub.createSystemLog(createSysRecordRequest);

            return BaseResponse.success(null);
        }catch(StatusRuntimeException e){
            log.error("ErrorCode : " + e.getStatus().getCode());
            return BaseResponse.error(e.getMessage());
        }catch(Exception e){
            e.printStackTrace();
            return BaseResponse.error(e.getMessage());
        }
    }
    
    @PostMapping("/v1/list_systemlog")
    public BaseResponse listSystemLog(@RequestBody SystemLogQueryRequest systemLogRequest){
        try{
            //构建查询参数
            ListSystemLogResponse listSystemLogResponse = iSysRecordServiceBlockingStub.listSystemLogByCondition(null);
            return BaseResponse.success(vos);

        }catch(StatusRuntimeException e){
            log.error("ErrorCode : " + e.getStatus().getCode());
            return BaseResponse.error(e.getMessage());
        }catch(Exception e){
            e.printStackTrace();
            return BaseResponse.error(e.getMessage());
        }
    }

}

3.4、服务端配置

修改【base-grpc-framework-application】模块,在这个专题中,与应用相关的配置全在base-grpc-framework-application模块的application.yml中配置,因为是本地运行,所以这里我们只修改application-dev.yml。修改后的源码如下:

# http配置
server:
  compression:
    enabled: true
    mime-types: application/json,application/octet-stream

# spring配置
spring:
  application:
    name: GrpcFramework-Server-APP

# grpc Server配置
grpc:
  server:
    port: 9898   #发布远程访问地址
    in-process-name: native  #发布本地访问地址
  client:
    inProcess:
      address: in-process:native #配置内部访问服务名称

上面配置注意grpc.server.in-process-name和grpc.client.inprocess.adderss一定要一样,而且要与controller中的@GrpcClient("inProcess")配置一样,其中grpc.client.inprocess.adderss后面内容in-process:native中的in-process:是固定的写法,不能更改。

3.5、GRPC服务端测试

启动BaseFrameworkApplication.java,启动时不要忘记配置环境变量为dev,配置方式可参考笔者的上一章文章​​https://blog.51cto.com/arch/5378945​​。

3.5.1、grpc测试

可先安装grpcurl工具,笔者使用的是mac系统,安装方式如下,其它系统可网上查询:

brew install grpcui  #安装
grpcui -plaintext 127.0.0.1:9898  #运行

执行上面运行命令后,会自动打开浏览器,显示如下图所示,测试方式可自行操作下:

基于grpc从零开始搭建一个准生产分布式应用(3) - GRPC实现_第4张图片

3.5.2、controller测试

在浏览器中输入 ​​http://localhost:8080/swagger-ui.html​​  ,下图中红框外的两个controller是笔者测试用的,可忽略。红框内的内容就是上面小节写的controller。展开后就可进行测试了:

基于grpc从零开始搭建一个准生产分布式应用(3) - GRPC实现_第5张图片

四、GRPC服务端拦截器实现

这个拦截器主要是实现了日志打印功能,通用track的方式来跟踪每一次访问,详细设计思路可参考笔者写的另一篇文章的描述 ​​https://blog.51cto.com/arch/5295170​​。

完整的类源码如下所下图所示,源码可以查看:​​基于grpc从零开始搭建一个准生产分布式应用(0) - 07 - 附:GRPC拦截器源码​​。

基于grpc从零开始搭建一个准生产分布式应用(3) - GRPC实现_第6张图片

五、GRPC实现-客户端

修改【base-grpc-framework-client】模块。client端不是太重点,这里只是为了说明一下调用和配置方式,笔者的代码没有配置启动类,感兴趣的同学可按第3章的内容自行配置springboot来启动。也可跳过此章,了解下内容即可。完整代码如下图所示:

基于grpc从零开始搭建一个准生产分布式应用(3) - GRPC实现_第7张图片

5.1、修改pom.xml文件



    
        base-grpc-framework-parent
        com.zd
        1.0-SNAPSHOT
    
    4.0.0

    base-grpc-framework-client

    
        0.1.0
    

    
        
        
            net.devh
            grpc-client-spring-boot-starter
        
        
            com.google.protobuf
            protobuf-java-util
        
        
            com.google.protobuf
            protobuf-java
        

        
        
            com.zd
            base-grpc-framework-common
            ${project.parent.version}
        
        
            com.zd
            base-grpc-framework-api
            ${project.parent.version}
        

        
        
        
            org.projectlombok
            lombok
            compile
        

        
        
            cn.hutool
            hutool-all
        

        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        
    

5.2、Controller实现

同3.3节的实现一样,只是@GrpcClient("local-grpc-server")中的内容不一样,可参考5.3节的配置。

@Slf4j
@RestController
@RequestMapping("/systemlog")
public class SystemLogController {

    @GrpcClient("local-grpc-server")
    private ISystemLogServiceGrpc.ISystemLogServiceBlockingStub iSysRecordServiceBlockingStub;

    @GetMapping("/v1/create_systemlog")
    public BaseResponse listWorkFlowByStatus(
            @RequestParam(value="biz_id") String bizId,
            @RequestParam(value="user_id") Long userId,
            @RequestParam(value="code") String code,
            @RequestParam(value="custom_code", required=false) String customCode){
        try{
            //构建查询参数
            CreateSystemLogRequest createSysRecordRequest  = CreateSystemLogRequest.newBuilder()
                    .setBizId(StringValue.of(bizId))
                    .setUserId(Int64Value.of(userId))
                    .setCode(StringValue.of(code))
                    .setCustomCode(StringValue.of(customCode))
                    .build();

            SystemLogOperatorResponse response = iSysRecordServiceBlockingStub.createSystemLog(createSysRecordRequest);

            return BaseResponse.success(null);
        }catch(StatusRuntimeException e){
            log.error("ErrorCode : " + e.getStatus().getCode());
            return BaseResponse.error(e.getMessage());
        }catch(Exception e){
            e.printStackTrace();
            return BaseResponse.error(e.getMessage());
        }
    }
}

5.3、客户端配置

在base-grpc-framework-client】新建一个yml文件。

server:
  port: 8089
spring:
  application:
    name: GrpcFramework-Client-APP
grpc:
  client:
    local-grpc-server:
      address: 'static://127.0.0.1:9898'
      enableKeepAlive: true
      keepAliveWithoutCalls: true

下一章节会先讲述proto的内容。

你可能感兴趣的:(java,微服务,领域驱动设计,rpc,springboot)