本章开始会进入GRPC子专题,先实现前面章节中提到的例子。然后就使用的知识点展开全面的描述。本章代码任务:1、实现一个简单的GRPC服务;2、实现GRPC拦截器。
本章的代码承接上一章的代码进行迭代。因模块间存在相互依赖关系,读者一定先按笔者讲述的顺序操作,否则最后程序可能由于依赖问题导致不能运行;
因为本专题定位于准生产环境,所以我们在代码上也会按规范严格要求一下,本章涉及的准备工作就是在common模块优先定义一些辅助工具类。因代码比较多,详细可查看:
修改【base-grpc-framework-api】模块,我们用proto方式来实现,首先要修改pom.xml文件引入必要的依赖,再定义一个.proto文件。proto的使用在下一章节会详细介绍,本章了解下必要的知识点即可。
这块没有需要注意的地方,用下列内容覆盖上一章中的源码即可。源码中的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
文件路径: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;
}
把.proto文件编译成java代码,在idea中的操作过程共分为三步:
先确定已经按2.2节内容正确配置了protobuf-maven-plugin插件后,然后先后双击下图中红框的内容:
最后打开源码的target文件夹,会看到生成的Java源码文件,如下图所示,grpc-java文件夹中存储的是接口定义即proto文件中service标签中定义的内容,java文件中存储的是message标签定义的内容,至此api模块的所有修改就完成了:
本章我们会传承上一章节的代码,需改动下图中标红的模块。服务端的实现稍复杂一些,同样也要改一些pom配置,本次我们会实现两个接口,同学们可以了解下grpc本地调用的实现方法,同样grpc详细的内容也会在后续章节详细展开。
修改【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.target}
org.projectlombok
lombok
${lombok.version}
org.mapstruct
mapstruct-processor
${mapstruct.version}
org.projectlombok
lombok-mapstruct-binding
${lombok.mapstruct.binding.version}
修改【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());
}
}
}
修改【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());
}
}
}
修改【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:是固定的写法,不能更改。
启动BaseFrameworkApplication.java,启动时不要忘记配置环境变量为dev,配置方式可参考笔者的上一章文章https://blog.51cto.com/arch/5378945。
可先安装grpcurl工具,笔者使用的是mac系统,安装方式如下,其它系统可网上查询:
brew install grpcui #安装
grpcui -plaintext 127.0.0.1:9898 #运行
执行上面运行命令后,会自动打开浏览器,显示如下图所示,测试方式可自行操作下:
在浏览器中输入 http://localhost:8080/swagger-ui.html ,下图中红框外的两个controller是笔者测试用的,可忽略。红框内的内容就是上面小节写的controller。展开后就可进行测试了:
这个拦截器主要是实现了日志打印功能,通用track的方式来跟踪每一次访问,详细设计思路可参考笔者写的另一篇文章的描述 https://blog.51cto.com/arch/5295170。
完整的类源码如下所下图所示,源码可以查看:基于grpc从零开始搭建一个准生产分布式应用(0) - 07 - 附:GRPC拦截器源码。
修改【base-grpc-framework-client】模块。client端不是太重点,这里只是为了说明一下调用和配置方式,笔者的代码没有配置启动类,感兴趣的同学可按第3章的内容自行配置springboot来启动。也可跳过此章,了解下内容即可。完整代码如下图所示:
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
同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());
}
}
}
在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的内容。